Repository: emacs-eask/cli Branch: master Commit: 43e89e0178d6 Files: 519 Total size: 1.2 MB Directory structure: gitextract_of7ukk9b/ ├── .dir-locals.el ├── .gitattributes ├── .github/ │ ├── dependabot.yml │ ├── labeler.yml │ ├── scripts/ │ │ ├── setup-eask │ │ └── setup-eask.ps1 │ └── workflows/ │ ├── analyze.yml │ ├── build.yml │ ├── compat.yml │ ├── compile.yml │ ├── config.yml │ ├── deprecated/ │ │ ├── .gitkeep │ │ ├── color.yml │ │ └── error.yml │ ├── disabled/ │ │ └── .gitkeep │ ├── docker.yml │ ├── docs.yml │ ├── emacs.yml │ ├── exec.yml │ ├── exit_status.yml │ ├── global.yml │ ├── install.yml │ ├── labeler.yml │ ├── link.yml │ ├── local.yml │ ├── options.yml │ ├── outdated_upgrade.yml │ ├── search.yml │ ├── test_buttercup.yml │ ├── test_ecukes.yml │ ├── test_ert-runner.yml │ ├── test_ert.yml │ ├── update_submodules.yml │ ├── upgrade-eask.yml │ └── webinstall.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Dockerfile ├── Eask ├── LICENSE ├── Makefile ├── PKGBUILD ├── README.md ├── bin/ │ ├── eask │ └── eask.bat ├── cmds/ │ ├── clean/ │ │ ├── all.js │ │ ├── autoloads.js │ │ ├── dist.js │ │ ├── elc.js │ │ ├── log-file.js │ │ ├── pkg-file.js │ │ └── workspace.js │ ├── core/ │ │ ├── analyze.js │ │ ├── archives.js │ │ ├── bump.js │ │ ├── cat.js │ │ ├── clean.js │ │ ├── compile.js │ │ ├── concat.js │ │ ├── create.js │ │ ├── docker.js │ │ ├── docs.js │ │ ├── emacs.js │ │ ├── eval.js │ │ ├── exec-path.js │ │ ├── exec.js │ │ ├── files.js │ │ ├── format.js │ │ ├── generate.js │ │ ├── info.js │ │ ├── init.js │ │ ├── install-deps.js │ │ ├── install-file.js │ │ ├── install-vc.js │ │ ├── install.js │ │ ├── keywords.js │ │ ├── link.js │ │ ├── lint.js │ │ ├── list.js │ │ ├── load-path.js │ │ ├── load.js │ │ ├── loc.js │ │ ├── outdated.js │ │ ├── package-directory.js │ │ ├── package.js │ │ ├── recipe.js │ │ ├── recompile.js │ │ ├── refresh.js │ │ ├── reinstall.js │ │ ├── repl.js │ │ ├── run.js │ │ ├── search.js │ │ ├── source.js │ │ ├── status.js │ │ ├── test.js │ │ ├── uninstall.js │ │ └── upgrade.js │ ├── create/ │ │ ├── el-project.js │ │ ├── elpa.js │ │ └── package.js │ ├── docs/ │ │ └── el2org.js │ ├── format/ │ │ ├── elfmt.js │ │ └── elisp-autofmt.js │ ├── generate/ │ │ ├── autoloads.js │ │ ├── ignore.js │ │ ├── license.js │ │ ├── pkg-file.js │ │ ├── recipe.js │ │ ├── test/ │ │ │ ├── buttercup.js │ │ │ ├── ecukes.js │ │ │ ├── ert-runner.js │ │ │ └── ert.js │ │ ├── test.js │ │ ├── workflow/ │ │ │ ├── circle-ci.js │ │ │ ├── github.js │ │ │ ├── gitlab.js │ │ │ └── travis-ci.js │ │ └── workflow.js │ ├── link/ │ │ ├── add.js │ │ ├── delete.js │ │ └── list.js │ ├── lint/ │ │ ├── checkdoc.js │ │ ├── declare.js │ │ ├── elint.js │ │ ├── elisp-lint.js │ │ ├── elsa.js │ │ ├── indent.js │ │ ├── keywords.js │ │ ├── license.js │ │ ├── org.js │ │ ├── package.js │ │ └── regexps.js │ ├── run/ │ │ ├── command.js │ │ └── script.js │ ├── source/ │ │ ├── add.js │ │ ├── delete.js │ │ └── list.js │ ├── test/ │ │ ├── activate.js │ │ ├── buttercup.js │ │ ├── ecukes.js │ │ ├── ert-runner.js │ │ ├── ert.js │ │ └── melpazoid.js │ └── util/ │ ├── locate.js │ ├── root.js │ └── upgrade-eask.js ├── docs/ │ ├── .gitignore │ ├── config/ │ │ └── _default/ │ │ ├── config.yaml │ │ └── languages.yaml │ ├── content/ │ │ ├── Continuous-Integration/ │ │ │ ├── CircleCI/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── GitHub-Actions/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── GitLab-Runner/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Travis-CI/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── Contributing/ │ │ │ ├── Codebase-Overview/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Developing-Eask/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Documentation/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── How-to-Contribute/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── PR/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── DSL/ │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── Development-API/ │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── Examples/ │ │ │ ├── Emacs-Configuration/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Package-Development/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Real-project-examples/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── FAQ/ │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── Getting-Started/ │ │ │ ├── Advanced-Usage/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Basic-Usage/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Commands-and-options/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Directory-Structure/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Finding-Emacs/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Install-Eask/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Introduction/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── Quick-Start/ │ │ │ │ ├── _index.en.md │ │ │ │ └── _index.zh-tw.md │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── License/ │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── Troubleshooting/ │ │ │ ├── _index.en.md │ │ │ └── _index.zh-tw.md │ │ ├── _index.en.md │ │ ├── _index.zh-tw.md │ │ ├── tos.en.md │ │ └── tos.zh-tw.md │ ├── data/ │ │ └── menu/ │ │ ├── extra.yaml │ │ └── more.yaml │ ├── etc/ │ │ ├── execution order.drawio │ │ └── scopes.drawio │ ├── layouts/ │ │ └── partials/ │ │ └── head/ │ │ └── custom.html │ └── static/ │ ├── custom.css │ └── main.js ├── eask.js ├── flake.nix ├── lisp/ │ ├── _prepare.el │ ├── clean/ │ │ ├── all.el │ │ ├── autoloads.el │ │ ├── dist.el │ │ ├── elc.el │ │ ├── log-file.el │ │ ├── pkg-file.el │ │ └── workspace.el │ ├── core/ │ │ ├── analyze.el │ │ ├── archives.el │ │ ├── bump.el │ │ ├── cat.el │ │ ├── compile.el │ │ ├── concat.el │ │ ├── emacs.el │ │ ├── eval.el │ │ ├── exec-path.el │ │ ├── exec.el │ │ ├── files.el │ │ ├── info.el │ │ ├── init.el │ │ ├── install-deps.el │ │ ├── install-file.el │ │ ├── install-vc.el │ │ ├── install.el │ │ ├── keywords.el │ │ ├── list.el │ │ ├── load-path.el │ │ ├── load.el │ │ ├── loc.el │ │ ├── outdated.el │ │ ├── package-directory.el │ │ ├── package.el │ │ ├── recipe.el │ │ ├── recompile.el │ │ ├── refresh.el │ │ ├── reinstall.el │ │ ├── repl.el │ │ ├── search.el │ │ ├── status.el │ │ ├── uninstall.el │ │ └── upgrade.el │ ├── create/ │ │ ├── el-project.el │ │ ├── elpa.el │ │ └── package.el │ ├── docs/ │ │ └── el2org.el │ ├── extern/ │ │ ├── ansi.el │ │ ├── github-elpa.el │ │ ├── package-build/ │ │ │ ├── 24/ │ │ │ │ ├── package-build-badges.el │ │ │ │ ├── package-build.el │ │ │ │ ├── package-recipe-mode.el │ │ │ │ └── package-recipe.el │ │ │ ├── 25/ │ │ │ │ ├── package-build-badges.el │ │ │ │ ├── package-build.el │ │ │ │ ├── package-recipe-mode.el │ │ │ │ └── package-recipe.el │ │ │ ├── 26/ │ │ │ │ ├── package-build-badges.el │ │ │ │ ├── package-build.el │ │ │ │ ├── package-recipe-mode.el │ │ │ │ └── package-recipe.el │ │ │ └── README.md │ │ ├── package-build.el │ │ ├── package-recipe.el │ │ └── package.el │ ├── format/ │ │ ├── elfmt.el │ │ └── elisp-autofmt.el │ ├── generate/ │ │ ├── autoloads.el │ │ ├── ignore.el │ │ ├── license.el │ │ ├── pkg-file.el │ │ ├── recipe.el │ │ ├── test/ │ │ │ ├── buttercup.el │ │ │ ├── ecukes.el │ │ │ ├── ert-runner.el │ │ │ └── ert.el │ │ └── workflow/ │ │ ├── circle-ci.el │ │ ├── github.el │ │ ├── gitlab.el │ │ └── travis-ci.el │ ├── help/ │ │ ├── core/ │ │ │ ├── analyze │ │ │ ├── bump │ │ │ ├── compile │ │ │ ├── concat │ │ │ ├── docs │ │ │ ├── eval │ │ │ ├── exec │ │ │ ├── info │ │ │ ├── init │ │ │ ├── install │ │ │ ├── install-deps │ │ │ ├── install-file │ │ │ ├── install-vc │ │ │ ├── recipe │ │ │ ├── reinstall │ │ │ ├── search │ │ │ └── uninstall │ │ ├── format/ │ │ │ ├── elfmt │ │ │ └── elisp-autofmt │ │ ├── init/ │ │ │ ├── cask │ │ │ ├── eldev │ │ │ ├── keg │ │ │ └── source │ │ ├── link/ │ │ │ ├── add/ │ │ │ │ ├── error │ │ │ │ └── success/ │ │ │ │ ├── eask │ │ │ │ └── pkg │ │ │ └── delete │ │ ├── lint/ │ │ │ ├── checkdoc │ │ │ ├── declare │ │ │ ├── elint │ │ │ ├── elisp-lint │ │ │ ├── elsa │ │ │ ├── indent │ │ │ ├── keywords │ │ │ ├── keywords-file │ │ │ ├── keywords-header │ │ │ ├── org │ │ │ ├── package │ │ │ └── regexps │ │ ├── run/ │ │ │ ├── command │ │ │ └── script │ │ ├── source/ │ │ │ ├── add │ │ │ └── delete │ │ └── test/ │ │ ├── ert │ │ └── melpazoid │ ├── init/ │ │ ├── cask.el │ │ ├── eldev.el │ │ ├── keg.el │ │ └── source.el │ ├── link/ │ │ ├── add.el │ │ ├── delete.el │ │ └── list.el │ ├── lint/ │ │ ├── checkdoc.el │ │ ├── declare.el │ │ ├── elint.el │ │ ├── elisp-lint.el │ │ ├── elsa.el │ │ ├── indent.el │ │ ├── keywords.el │ │ ├── license.el │ │ ├── org.el │ │ ├── package.el │ │ └── regexps.el │ ├── run/ │ │ ├── command.el │ │ └── script.el │ ├── source/ │ │ ├── add.el │ │ ├── delete.el │ │ └── list.el │ ├── test/ │ │ ├── activate.el │ │ ├── buttercup.el │ │ ├── ecukes.el │ │ ├── ert-runner.el │ │ ├── ert.el │ │ └── melpazoid.el │ └── util/ │ └── root.el ├── package.json ├── src/ │ ├── env.js │ └── util.js ├── test/ │ ├── color/ │ │ ├── .gitignore │ │ ├── Eask │ │ ├── color.el │ │ └── run.sh │ ├── development/ │ │ ├── compat.el │ │ └── compile/ │ │ └── test-redefine.el │ ├── error/ │ │ ├── .gitignore │ │ ├── Eask │ │ └── run.sh │ ├── fixtures/ │ │ ├── home/ │ │ │ ├── .emacs.d/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Eask │ │ │ │ ├── RADME.md │ │ │ │ └── init.el │ │ │ ├── .gitignore │ │ │ ├── Eask │ │ │ └── scripts/ │ │ │ ├── setup.ps1 │ │ │ ├── setup.sh │ │ │ └── testing.sh │ │ ├── mini.pkg.1/ │ │ │ ├── Eask │ │ │ ├── RADME.md │ │ │ ├── files/ │ │ │ │ ├── mini.pkg.1-1.el │ │ │ │ └── mini.pkg.1-2.el │ │ │ └── mini.pkg.1.el │ │ └── mini.pkg.2/ │ │ ├── Eask │ │ ├── RADME.md │ │ ├── files/ │ │ │ ├── mini.pkg.2-1.el │ │ │ └── mini.pkg.2-2.el │ │ └── mini.pkg.2.el │ └── jest/ │ ├── README.md │ ├── __snapshots__/ │ │ └── analyze.test.js.snap │ ├── analyze/ │ │ ├── dsl/ │ │ │ ├── .gitignore │ │ │ ├── Eask │ │ │ └── check-dsl.el │ │ ├── errors/ │ │ │ ├── Eask-error │ │ │ ├── Eask-lexical │ │ │ ├── Eask-normal │ │ │ └── Eask-warn │ │ └── metadata/ │ │ ├── .gitignore │ │ ├── Eask │ │ └── check-metadata.el │ ├── analyze.test.js │ ├── buttercup/ │ │ ├── .gitignore │ │ ├── Eask │ │ ├── test-fail/ │ │ │ └── buttercup-test.el │ │ └── test-ok/ │ │ └── buttercup-test.el │ ├── compile/ │ │ ├── Eask │ │ ├── fail.el │ │ └── mock.el │ ├── compile.test.js │ ├── config/ │ │ └── init.el │ ├── config.test.js │ ├── docker/ │ │ ├── Eask │ │ ├── RADME.md │ │ ├── files/ │ │ │ ├── mini.pkg.1-1.el │ │ │ └── mini.pkg.1-2.el │ │ └── mini.pkg.1.el │ ├── docker.test.js │ ├── ecukes/ │ │ ├── Eask │ │ └── features/ │ │ ├── foo.feature │ │ ├── step-definitions/ │ │ │ └── ecukes-steps.el │ │ └── support/ │ │ └── env.el │ ├── emacs.test.js │ ├── empty/ │ │ └── .gitkeep │ ├── ert/ │ │ ├── .gitignore │ │ ├── Eask │ │ ├── test/ │ │ │ └── ert-test.el │ │ └── test-nil-message/ │ │ └── ert-test.el │ ├── ert-runner/ │ │ ├── .gitignore │ │ ├── Eask │ │ └── test/ │ │ └── ert-runner-test.el │ ├── exec/ │ │ ├── .gitignore │ │ ├── Eask │ │ ├── bin/ │ │ │ └── github-elpa.bat │ │ ├── exec.el │ │ └── test/ │ │ └── test-dummy.el │ ├── exec.test.js │ ├── exit-status.test.js │ ├── global/ │ │ └── Eask │ ├── global.test.js │ ├── helpers.js │ ├── helpers.test.js │ ├── install/ │ │ ├── Eask │ │ ├── RADME.md │ │ ├── files/ │ │ │ ├── mini.pkg.1-1.el │ │ │ └── mini.pkg.1-2.el │ │ ├── foo-mode/ │ │ │ ├── Eask │ │ │ └── foo-mode.el │ │ ├── foo-no/ │ │ │ ├── Eask │ │ │ └── foo-no.el │ │ ├── mini.pkg.1.el │ │ └── mini.pkg.2/ │ │ └── mini.pkg.2.el │ ├── install.test.js │ ├── link/ │ │ ├── .gitkeep │ │ ├── Eask │ │ ├── link-fail/ │ │ │ ├── Eask │ │ │ ├── RADME.md │ │ │ ├── files/ │ │ │ │ ├── mini.pkg.1-1.el │ │ │ │ └── mini.pkg.1-2.el │ │ │ └── mini.pkg.1.el │ │ └── link-to/ │ │ ├── Eask │ │ └── link-to.el │ ├── link.test.js │ ├── lint/ │ │ ├── Eask │ │ ├── checkdoc-fail.el │ │ ├── declare-fail.el │ │ ├── declare-ok.el │ │ ├── elisp-lint-ok.el │ │ ├── elsa-warn.el │ │ ├── indent-warn.el │ │ ├── keywords-bad/ │ │ │ ├── Eask │ │ │ └── keywords-ok.el │ │ ├── keywords-ok/ │ │ │ ├── Eask │ │ │ └── keywords-ok.el │ │ └── regexp-warn.el │ ├── lint.test.js │ ├── local/ │ │ ├── .gitignore │ │ ├── Eask │ │ ├── RADME.md │ │ ├── README.org │ │ ├── files/ │ │ │ ├── mini.pkg.1-1.el │ │ │ └── mini.pkg.1-2.el │ │ └── mini.pkg.1.el │ ├── local.test.js │ ├── options/ │ │ ├── .gitignore │ │ ├── Eask │ │ └── options.el │ ├── options.test.js │ ├── outdated-upgrade/ │ │ ├── .gitignore │ │ ├── Eask │ │ └── make-outdate.el │ ├── outdated-upgrade.test.js │ ├── search/ │ │ ├── .gitignore │ │ └── Eask │ ├── search.test.js │ ├── test-buttercup.test.js │ ├── test-ecukes.test.js │ ├── test-ert-runner.test.js │ ├── test-ert.test.js │ └── upgrade-eask.test.js └── webinstall/ ├── install.bat └── install.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dir-locals.el ================================================ ;;; Directory Local Variables ;;; For more information see (info "(emacs) Directory Variables") ((js-mode . ((lsp-enabled-clients . (jsts-ls)))) (markdown-mode . ((fill-column . 80)))) ================================================ FILE: .gitattributes ================================================ *.sh text eol=lf *.nix text eol=lf /bin/eask text eol=lf /eask.js text eol=lf ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'daily' # Maintain dependencies for npm - package-ecosystem: 'npm' directory: '/' schedule: interval: 'daily' ================================================ FILE: .github/labeler.yml ================================================ documentation: - docs/**/* - README.md - CHANGELOG.md CI: - .github/**/* ================================================ FILE: .github/scripts/setup-eask ================================================ #!/usr/bin/env bash npm install echo "$GITHUB_WORKSPACE/bin" >> "$GITHUB_PATH" ================================================ FILE: .github/scripts/setup-eask.ps1 ================================================ npm install echo "$PWD/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append ================================================ FILE: .github/workflows/analyze.yml ================================================ name: Analyze on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/analyze.test.js ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: VERSION: 0.12.10 REPO_BIN: 'emacs-eask/binaries' jobs: prepare: runs-on: ubuntu-latest steps: - uses: jcs-actions/delete-tag-and-release@master if: github.ref == 'refs/heads/master' continue-on-error: true with: delete_release: true tag_name: ${{ env.VERSION }} env: GITHUB_TOKEN: ${{ secrets.PAT }} build: needs: [prepare] runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - target: linux-arm64 ext: tar.gz - target: linux-x64 ext: tar.gz - target: macos-arm64 ext: tar.gz - target: macos-x64 ext: tar.gz - target: win-arm64 ext: zip - target: win-x64 ext: zip steps: - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update sudo apt-get install --assume-yes qemu-user-binfmt - uses: MOZGIII/install-ldid-action@master with: tag: v2.1.5-procursus7 - name: Preparing... run: npm install --include=dev - name: Building... run: npm run-script pkg-${{ matrix.target }} - name: Prepare content... run: | mv lisp dist mv LICENSE dist mv README.md dist - name: Change permissions (Unix) if: contains(fromJSON('["tar.gz"]'), matrix.ext) run: | chmod -R 777 ./dist - name: Tar dist (Unix) if: contains(fromJSON('["tar.gz"]'), matrix.ext) run: | tar -czf ${{ matrix.target }}.${{ matrix.ext }} -C ./dist/ . cp ${{ matrix.target }}.${{ matrix.ext }} eask_${{ env.VERSION }}_${{ matrix.target }}.${{ matrix.ext }} - name: Zipping dist (Windows) if: contains(fromJSON('["zip"]'), matrix.ext) working-directory: dist run: | zip -r ${{ matrix.target }}.${{ matrix.ext }} . mv ${{ matrix.target }}.${{ matrix.ext }} ../ cd .. cp ${{ matrix.target }}.${{ matrix.ext }} eask_${{ env.VERSION }}_${{ matrix.target }}.${{ matrix.ext }} - name: Upload for prerelease if: github.ref == 'refs/heads/master' uses: ncipollo/release-action@v1.21.0 with: token: ${{ secrets.PAT }} tag: ${{ env.VERSION }} allowUpdates: true prerelease: true draft: false artifacts: eask_${{ env.VERSION }}_${{ matrix.target }}.${{ matrix.ext }} #### Upload an artifact for testing purposes - name: Upload for tests uses: actions/upload-artifact@v7 with: name: ${{ matrix.target }} path: dist #### Prepare to push to `binaries` repository! - name: Move tar to HOME run: mv ${{ matrix.target }}.${{ matrix.ext }} ~/ - name: Checkout binaries repository uses: actions/checkout@v6 with: repository: ${{ env.REPO_BIN }} persist-credentials: false fetch-depth: 1 - name: Clean up previous binaries continue-on-error: true run: rm -rf eask/${{ matrix.target }}.${{ matrix.ext }} - name: Move binaries to repository run: mv ~/${{ matrix.target }}.${{ matrix.ext }} ./ - name: Set git config run: | git config user.name github-actions git config user.email github-actions@github.com - name: Commit continue-on-error: true run: | git pull git add . git commit -m "Update binaries ${{ matrix.target }}.${{ matrix.ext }}" - name: Push if: github.ref == 'refs/heads/master' uses: jcs-actions/github-push-action@master with: repository: ${{ env.REPO_BIN }} github_token: ${{ secrets.PAT }} branch: master rebase: true retry: 7 ================================================ FILE: .github/workflows/compat.yml ================================================ name: Compat on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Test Emacs compatibility run: make test-compat ================================================ FILE: .github/workflows/compile.yml ================================================ name: Compile on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Test compile run: make test-redefine ================================================ FILE: .github/workflows/config.yml ================================================ name: Config on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/config.test.js ================================================ FILE: .github/workflows/deprecated/.gitkeep ================================================ ================================================ FILE: .github/workflows/deprecated/color.yml ================================================ name: Color on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - 'eask' - '**.yml' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v5 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | make color ================================================ FILE: .github/workflows/deprecated/error.yml ================================================ name: Error on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - 'eask' - '**.yml' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v5 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | make error ================================================ FILE: .github/workflows/disabled/.gitkeep ================================================ ================================================ FILE: .github/workflows/docker.yml ================================================ name: Docker on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest # XXX: `macos-latest` on arm64 is not possible as well!? # See https://stackoverflow.com/questions/77675906/github-actions-build-docker-image-on-arm64-macos-latest-xlarge #- macos-latest # XXX: `windows-latest` is not possible at the moment! #- windows-latest steps: - uses: actions/checkout@v6 - name: Install Docker (Linux) if: runner.os == 'Linux' uses: docker/setup-docker-action@v5 - name: Install Docker (macOS) if: runner.os == 'macOS' run: | brew install docker - name: Install Docker (Windows) if: runner.os == 'Windows' uses: crazy-max/ghaction-chocolatey@v4 with: args: install docker - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/docker.test.js ================================================ FILE: .github/workflows/docs.yml ================================================ name: Docs on: push: branches: - master - docs paths: - '**/docs.yml' - docs/** # ignore - '!**/build.yml' pull_request: branches: - master paths: - '**/docs.yml' - docs/** # ignore - '!**/build.yml' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: PATH_DOC: ${{ github.workspace }}/emacs-eask.github.io jobs: publish-doc: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: true - uses: actions/setup-node@v6 with: node-version: 22 - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: '0.134.2' - name: Build theme working-directory: ./docs/themes/geekdoc run: | npm install npm run build - name: Check out GitHub page repo uses: actions/checkout@v6 if: github.ref == 'refs/heads/master' with: repository: emacs-eask/emacs-eask.github.io path: ${{ env.PATH_DOC }} token: ${{ secrets.PAT }} - name: Clean up before the build working-directory: ${{ env.PATH_DOC }} continue-on-error: true run: git rm -r '*' - name: Build doc artifacts with Hugo working-directory: docs run: | mkdir -p ${{ env.PATH_DOC }} hugo --destination ${{ env.PATH_DOC }} --minify # TODO: Generate better commit message - name: Publish doc artifacts if: github.ref == 'refs/heads/master' working-directory: ${{ env.PATH_DOC }} run: | git config user.name github-actions git config user.email github-actions@github.com git add . git commit -F- <<-_UBLT_COMMIT_MSG_ auto: ${{ github.event.head_commit.message }} SourceCommit: https://github.com/emacs-eask/cli/commit/${{ github.sha }} _UBLT_COMMIT_MSG_ git push ================================================ FILE: .github/workflows/emacs.yml ================================================ name: Emacs on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/emacs.test.js ================================================ FILE: .github/workflows/exec.yml ================================================ name: Exec on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/exec.test.js ================================================ FILE: .github/workflows/exit_status.yml ================================================ name: Exit Status on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: jcs090218/setup-emacs@master with: version: 30.2 - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/exit-status.test.js ================================================ FILE: .github/workflows/global.yml ================================================ name: Global on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/global.test.js ================================================ FILE: .github/workflows/install.yml ================================================ name: Install on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/install.test.js ================================================ FILE: .github/workflows/labeler.yml ================================================ name: "PR Labeler" on: - pull_request_target jobs: triage: runs-on: ubuntu-latest steps: - uses: actions/labeler@v4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true ================================================ FILE: .github/workflows/link.yml ================================================ name: Link on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/link.test.js ================================================ FILE: .github/workflows/local.yml ================================================ name: Local on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 26.3 - 27.2 - 28.2 - 29.4 - 30.2 experimental: [false] include: - os: ubuntu-latest emacs-version: snapshot experimental: true - os: macos-latest emacs-version: snapshot experimental: true - os: windows-latest emacs-version: snapshot experimental: true exclude: - os: macos-latest emacs-version: 26.3 - os: macos-latest emacs-version: 27.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/local.test.js ================================================ FILE: .github/workflows/options.yml ================================================ name: Options on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/options.test.js ================================================ FILE: .github/workflows/outdated_upgrade.yml ================================================ name: Outdated_Upgrade on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/outdated-upgrade.test.js ================================================ FILE: .github/workflows/search.yml ================================================ name: Search on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/search.test.js ================================================ FILE: .github/workflows/test_buttercup.yml ================================================ name: Test buttercup on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/test-buttercup.test.js ================================================ FILE: .github/workflows/test_ecukes.yml ================================================ name: Test ecukes on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/test-ecukes.test.js ================================================ FILE: .github/workflows/test_ert-runner.yml ================================================ name: Test ert-runner on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/test-ert-runner.test.js ================================================ FILE: .github/workflows/test_ert.yml ================================================ name: Test ert on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/test-ert.test.js ================================================ FILE: .github/workflows/update_submodules.yml ================================================ name: Update Submodules on: schedule: - cron: '0 * * * *' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 - name: Update submodules run: | git submodule init git submodule update --remote --merge - name: Create Pull Request uses: peter-evans/create-pull-request@v8 with: title: 'Update submodules' body: '' commit-message: 'Update all submodules' branch: update/submodules delete-branch: true ================================================ FILE: .github/workflows/upgrade-eask.yml ================================================ name: Upgrade Eask on: push: branches: - master paths: - lisp/** - cmds/** - src/** - test/** - '**.yml' - '**.json' - 'eask' # ignore - '!**/build.yml' - '!**/docs.yml' pull_request: branches: - master paths-ignore: - '**/build.yml' - '**/docs.yml' - '**.md' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: - 30.2 steps: - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} - uses: actions/checkout@v6 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 - name: Testing... run: | npm run test-unsafe test/jest/upgrade-eask.test.js ================================================ FILE: .github/workflows/webinstall.yml ================================================ name: Webinstall on: push: branches: - master paths: - webinstall/** pull_request: branches: - master paths: - webinstall/** workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v6 - name: Install Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ ./webinstall/install.sh - name: Install Eask (Windows) if: matrix.os == 'windows-latest' run: ./webinstall/install.bat - name: Testing... (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | ~/.local/bin/eask --version - name: Testing... (Windows) if: matrix.os == 'windows-latest' shell: cmd run: | %USERPROFILE%/.local/bin/eask.exe --version ================================================ FILE: .gitignore ================================================ # OS generated .DS_Store ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variable files .env .env.development.local .env.test.local .env.production.local .env.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # vuepress v2.x temp and cache directory .temp # Docusaurus cache and generated files .docusaurus # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* ### Node Patch ### # Serverless Webpack directories .webpack/ # Optional stylelint cache # SvelteKit build / generate output .svelte-kit ### Emacs Specific ### # internal test _test/ # emacs emacs_backtrace.txt # ignore generated files *.elc # eask packages .eask/ dist/ # tools .elsa/ # packaging *-autoloads.el *-pkg.el *.zip *.tar *.tar.gz ================================================ FILE: .gitmodules ================================================ [submodule "docs/themes/geekdoc"] path = docs/themes/geekdoc url = https://github.com/thegeeklab/hugo-geekdoc branch = main ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. ## 0.13.x (Unreleased) > Released N/A * fix(lisp): Fix line containing format placeholders ([#377](../../pull/377)) * feat: Add `root` command ([`2a3872b`](../../commit/2a3872b0ac842268eb69cca403ab3ad8cda72a21)) * fix(lisp/emacs): Respect Eask file settings when possible ([`1b5aaa1`](../../commit/1b5aaa121b5d6a39e3a0664006fc10a4b22c2e84)) * fix(lisp): Let buttercup tests handle exit code themselves ([#385](../../pull/385)) * fix(lisp): Set up paths regardless of the working environment ([#386](../../pull/386)) * feat(cmds): Move `el2org` to docs subcommand ([#388](../../pull/388)) * fix(lisp): Report error only inside the command's execution ([#389](../../pull/389)) * fix: Correct entry file name to `eask.js` instead of `eask` ([#397](../../pull/397)) * fix: Ensure that `eask.js` has Unix line-endings ([#398](../../pull/398)) * fix: Correctly parse operands from the command arguments ([#401](../../pull/401)) * feat: Add new source `eine` ([`da75d6b`](../../commit/da75d6bf06fbf804f33262724dc17c3672a68e46)) ## 0.12.x > Released Dec 02, 2025 * fix(install): Package installed calculation ([`a479d53`](../../commit/a479d5355dfc832286288b790338652e174d606d)) * fix(install-file): Get correct install package name ([#318](../../pull/318)) * feat: Add `:try` for `depends-on` DSL ([#319](../../pull/319)) * fix: Warning missing `lexical-binding` cookie in Eask-file ([#320](../../pull/320)) * fix(extern): Avoid patching compat functions ([`524c02d`](../../commit/524c02dc66e14449f8511d5cfc591e4fae91b3b2)) * feat(_prepare.el): Respect global/system-wide packages ([`e0732f2`](../../commit/e0732f26a179ccceed96528cc71d9903b2f5fe4e)) * fix(lisp/extern): Clean up `compat` ([`2b41f5d`](../../commit/2b41f5db4b5bbe145c9671f95850f79a00dcbd48)) * fix(lisp): Paint keywords with nested ansi codes ([#323](../../pull/323)) * feat(create): Add new command for `el-project` ([#325](../../pull/325)) * feat(lint): Add command for `org-lint` ([#328](../../pull/328)) * fix: Avoid using white color for light theme ([`8acdc65`](../../commit/8acdc65f464414ac2ba591d600dbca55ac7a5952)) * fix: Use `kill-current-buffer` instead ([#331](../../pull/331)) * fix: eask analyze should exit with 1 on errors ([#359](../../pull/359)) * fix: Handle force install properly ([#373](../../pull/373)) * feat(docker): Accept direct docker image name in version arg ([`b39a7d6`](../../commit/b39a7d62b53580cae5ef3e85e7d9048d5f40c960)) ## 0.11.x > Released Apr 03, 2025 * fix(init): Preview final Eask file ([`ee7c36c`](../../commit/ee7c36cbd0d934523825c9c9430c73b50d4a8666)) * fix(lint): match behavior of declare linter to checkdoc ([#272](../../pull/272)) * fix(test): ERT test fails on Emacs 30+ ([`cc1f4e1`](../../commit/cc1f4e15b7b40b986ad1a93f6e40d121340598de)) * fix(test): eask options should not be passed to buttercup ([#281](../../pull/281)) * feat(core): Lazy install on required packages ([#284](../../pull/284)) * feat(compile): Improve compile messages on error ([#285](../../pull/285)) * feat(test): Make `ert-runner` accepts `files` arguments ([`d9041ea`](../../commit/d9041eaa3d15c82d196e9264faf74660eaa117ff)) * fix(_prepare.el): Keywords cannot be parsed in Emacs 26.x ([`0cb755e`](../../commit/0cb755e0c18d37a59c76add905ea089c61b3a931)) * feat(src/env.js): Ensure `Emacs` env ([#289](../../pull/289)) * ci(build.yml): Try forked pkg to fix `arm64` build 2 ([#292](../../pull/292) and [#294](../../pull/294)) * feat: Set error status when help is printed ([#307](../../pull/307)) * feat: Add exit code specification ([`c49f53c`](../../commit/c49f53caa1f6ac94a9c8c884d70d0860f55728c1)) * feat(install): Add commands `install-file` and `install-vc` ([#317](../../pull/317)) * feat(install): Handle `--force` flag to overwrite installed packages ([#317](../../pull/317)) ## 0.10.x > Released Jun 13, 2024 * fix: Unsilent in Eask-file by default ([`40d82be`](../../commit/40d82becaf20f0851e5fc37aa17c5f6871c163a3)) * Make better use for `special` commands ([#206](../../pull/206)) * feat: Accept rest arguments after command line separator ([#207](../../pull/207)) * feat(_prepare.el): Add program temp buffer ([`6262821`](../../commit/6262821596edb48d4f3b9bc61405129e084350b1)) * fix(indent.el): Further improve indent-lint ([`c0bf56c`](../../commit/c0bf56ccc0c47846cd314067fde6ee4eeedee5aa)) * fix: Compatible to Emacs 26.x ([#209](../../pull/209)) * feat(repl): Add the Elisp REPL command ([#210](../../pull/210)) * fix(generate/workflow): Require emacs version ([`f643c5d`](../../commit/f643c5da8992bf8b93287578dd8f3b553398ad85)) * chore: Alias `pack` to `package` command ([`e209d7c`](../../commit/e209d7c8152a17de81613f09b380a2f5ad05697a)) * fix(build.yml): Release with `.tar.gz` instead of `.zip` on Unix-like system ([#214](../../pull/214) and [#216](../../pull/216)) * feat: Add `loc` command ([#227](../../pull/227)) * fix: Allow initialize in other scopes ([#229](../../pull/229)) * feat: Rename command `check-eask` to `analyze` ([#230](../../pull/230)) * feat(fmt): Add formatters ([#232](../../pull/232)) * feat(test): Add built-in `ecukes` test command ([#236](../../pull/236)) * fix: Don't pollute outer programs ([#239](../../pull/239)) * feat(generate/test): Add commands to setup test files ([#243](../../pull/243)) * feat: Add `docs` command ([#245](../../pull/245)) * feat(cmds): Add `recompile` command ([#254](../../pull/254)) * feat(eask): Use `strict` option ([#256](../../pull/256)) ## 0.9.x > Released Nov 17, 2023 * Add command to generate LICENSE file ([#155](../../pull/155)) * Add new DSLs `author` and `license` ([#156](../../pull/156)) * Add license linter ([#157](../../pull/157)) * Allow few commands' execution without any Eask-file ([#159](../../pull/159)) * Add command `cat` ([#160](../../pull/160)) * Improve messages while processing package transaction ([#164](../../pull/164)) * Improve messages while task cleaning all ([#165](../../pull/165)) * Add `clean` option for clean compile environment ([#166](../../pull/166)) * Split up `config` and `global` space into two different environments ([#167](../../pull/167)) * Add use of `--show-hidden` option ([#168](../../pull/168)) * Add command to generate `ignore` file ([#169](../../pull/169)) * Use standard output ([#170](../../pull/170)) * Fix non-displayable character, use ascii instead ([#172](../../pull/172)) * Use environment PATH to specify Emacs version to use ([#173](../../pull/173)) * Fix showing positional arguments in subcommand's help menu ([#174](../../pull/174)) * Add aliases for `pkg-file` command ([`0d35d76`](../../commit/0d35d762a12bd399657c2fdcb60541dcc0c8b5e0)) * Add option to init from `Keg`-file ([#182](../../pull/182)) * Add option to init from elisp source file ([#183](../../pull/183)) * Print archives progress ([#184](../../pull/184)) * Respect package file path in `autoload` command ([`44c0424`](../../commit/44c042445bba0dd071d9112e58549437b7ebd58d)) * fix(vcpkg): Use workaround for `MODULE_NOT_FOUND` error ([#187](../../pull/187)) * Add docker command ([#188](../../pull/188)) * Enter docker directly when no arguments ([#191](../../pull/191)) * Add command to generate recipe ([#192](../../pull/192)) * Add option to init from `Eldev`-file ([#193](../../pull/193)) * Add command `commnad` for custom commands ([#195](../../pull/195)) * Merge the two commands `run` and `command` ([#196](../../pull/196)) * Add `melpazoid` command ([#202](../../pull/202)) * Add `source` command and its subcommands ([#203](../../pull/203)) * Add `bump` command ([#204](../../pull/204)) * fix: Print with stdout ([#205](../../pull/205)) ## 0.8.x > Released Mar 08, 2023 * Handle Easkfile existence for command `eask init` ([`2f5edb3`](../../commit/2f5edb3d9437d4ba55d2f014ca1ba1b65075eed8)) * Generate log files in user directory ([`974a17e`](../../commit/974a17e3b573520a775f813c72ecbd30a32012bf)) * Make sure there is no eval error in the root layer ([`fbd8eb4`](../../commit/fbd8eb4b91dfbb614b2b59a1a2a7fea9f594313d)) * Recognize `bsdtar` for windows ([#64](../../pull/64)) * Add `-q`/`--quick` option for quickstart ([#67](../../pull/67)) * Pick Eask-file by Emacs version, allow multiple Eask-files ([#68](../../pull/68)) * Add command and directive for `run-script` ([#69](../../pull/69)) * Remove alias for `build` subcommand ([#70](../../pull/70)) * Merge `clean` commands to one subcommand ([#71](../../pull/71)) * Fix void function `eask-source` to `eask-f-source` ([#75](../../pull/75)) * Fix upcoming breaking changes from `package-build` ([#65](../../pull/65)) * Add support for `elisp-lint` ([#79](../../pull/79)) * Adapt `-a`/`--all` option for archives command ([#83](../../pull/83)) * Merge command `list-all` to list with `-a`/`--all` option ([#84](../../pull/84)) * Support JSON format with Eask-file linter ([#85](../../pull/85)) * Handle multiple Eask-files for `init` command ([#86](../../pull/86)) * Remove dependency `s.el` ([`962dd5f`](../../commit/962dd5f8d0da1443368ac2d79b0a013c153a804e)) * Acknowledge cleaning state for `clean all` command ([#87](../../pull/87)) * Fix keyword lint undetected ([#88](../../pull/88)) * Improve Eask-file loader to search file upward ([#89](../../pull/89)) * Move command `activate` under test subcommand ([#92](../../pull/92)) * Add command to create ELPA project ([#94](../../pull/94)) * Add command to clean log files ([#97](../../pull/97)) * Add command to clean autoloads file ([#98](../../pull/98)) * Add command to clean pkg-file ([#99](../../pull/99)) * Add option `-o`/`--output` to output log for Eask checker ([#100](../../pull/100)) * Fix line-endings for unix system ([#106](../../pull/106)) * Add `link` command ([#107](../../pull/107)) * Fix package `xxx` is unavailable issue from Emacs snapshots ([#111](../../pull/111)) * Fix install `CRLF` EOL ([#112](../../pull/112)) * fix: Also expose `bin` folder for symlink package ([#115](../../pull/115)) * Resolve infinite recursion in `exec-path` setup ([#118](../../pull/118)) * feat: Add capability to search through path ([#119](../../pull/119)) * Support DSL `package-descriptor` ([#124](../../pull/124)) * Resolve the potential symlink to the bash script ([#24](../../pull/24), [#125](../../pull/125), and [#126](../../pull/126)) * Workaround for arguments that contain whitespaces ([#128](../../pull/128) and [#129](../../pull/129)) * Silent unnecessary Node's stacktrace ([#134](../../pull/134) and [#136](../../pull/136)) * Allow linter list through all errors ([#134](../../pull/134) and [#137](../../pull/137)) * Omit `nil` value for conditions in `development` scope ([#143](../../pull/143) and [#144](../../pull/144)) * Move `pkg-file` and `autoloads` commands under `generate` subcommand ([#142](../../pull/142)) * Add option to convert Cask to Eask [#141](../../pull/141) and [#145](../../pull/145)) * Add command to generate GHA workflow ([#141](../../pull/141) and [#146](../../pull/146)) * Add commands to support all kind of CI platforms ([#150](../../pull/150)) ## 0.7.x > Released Sep 08, 2022 * Avoid loading package info unless it's needed ([#13](../../pull/13), [#14](../../pull/14), and [#19](../../pull/19)) * Read `-pkg.el` file prior to package-file while exists ([#21](../../pull/21)) * Simplify (rewrite) command exec ([#22](../../pull/22)) * Move `exec` command to node layer ([#27](../../pull/27)) * Move `test` and `lint` commands to it's subcommand ([#31](../../pull/31)) * Avoid printing asni sequence in DUMB terminal ([#34](../../pull/34)) * Add implementation for `package-build--get-timestamp` ([#36](../../pull/36)) * Fix `time-convert` missing in Emacs 26.x; improve CI test on `package` command ([#38](../../pull/38)) * Fix `eask create`, honour `version` and `emacs_version` ([#41](../../pull/41)) * Add new command `eask emacs` ([#46](../../pull/46)) * Remove two options contradict to default settings, `--no-timestamps` and `--no-log-level` ([#48](../../pull/48)) * Add new option `--elapsed-time`, `--et` ([#48](../../pull/48)) * Add two new DSL; `website-url` and `keywords` ([#49](../../pull/49)) * Replace `time-convert` to compatible with older Emacs version ([#50](../../pull/50)) * Fix test buttercup with adding the current path to `load-path` ([#53](../../pull/53)) * Return correct packaged file depends on multi-files flag ([`23cd251`](../../commit/23cd251abd4e65c62549feb280e785d1ee9634a7)) * Add keywords command and it's linter ([#54](../../pull/54)) * Handle many-to-one condition for version string ([`0ac654f`](../../commit/0ac654face0557ff74c4a6048fc8bef59c915ec8)) * Add new option `--log-file`, `--lf` to generate log files ([#57](../../pull/57)) * Rename package to `@emacs-eask/cli` ([#60](../../pull/60)) ## 0.6.17 > Released May 05, 2022 * Reset `command-line-args-left` variable by default ([`fbda232`](../../commit/fbda232e962e3566efb94d8061a4035709b7c6e2)) * Clean up `.git` directory after creating a new elisp project ([`d845fd2`](../../commit/d845fd281707f0ed47fa955139e1ebddd56b8f58)) * Add new commnad `eask activate` ([`dab4321`](../../commit/dab4321803315eac530afa04161bd315687c271a)) * Make `default-directory` respect to `-g` flag ([`ffecfda`](../../commit/ffecfda6c982f145ccbd176fd02cc8bfb44352d7)) * Add new command `eask recipe` ([`3073fe8`](../../commit/3073fe89140a93fa91b8e81c2535240fb9e3ada1)) * Add new command `eask elsa` ([`69b5f15`](../../commit/69b5f1519fb5a2493fb26f96ee3ef022526aa6a1)) * Add `pkg` configuration, we can now pack to executables ([`79d2c45`](../../commit/79d2c454a14babc85fe3d8ae861be26e635ee12b)) * Don't inhibit message for `print`, `princ`, or `prin1`; this is dangerous ([`1297f78`](../../commit/1297f786fcebcae3ba743792fc6ff9706e8dfa6d)) * Fix path for `pkg` build ([`b97bfa8`](../../commit/b97bfa8cc46cedd967243a55d8fae83f106b081c)) ## 0.6.x > Released Apr 29, 2022 * Clean up `ansi-apply`, now accept object for the first argument ([`67277ff`](../../commit/67277ffb70ff8966d452da76fd50ad3cfb41ef08)) * Ensure Eask-file exists before executing the command ([`d40e29a`](../../commit/d40e29a042c1886d5748480d789a8d7b36735a09)) * Add checker rules for package metadata ([`f69efac`](../../commit/f69efac5e1f66ee73424e17d733cd732d3a28eda)) * Add checker rules for unmatch dependencies ([`ab42f7f`](../../commit/ab42f7f39dd0db6cbbbdfbfec5e7fae397936b99)) * Print more information for command `eask info` ([`6d74139`](../../commit/6d74139fe16c5bdddd0942b66bad21c3b34c6f5a)) * Print archive name while listing packages ([`b1cacc7`](../../commit/b1cacc71c48821142d41e5a740e7f09ba5b37bb1)) * Set main file for package lint ([`cfd4728`](../../commit/cfd47289bea936163c133bcd4b68d9e6b758a356)) * Print total files with command `eask info` ([`fe8dbe2`](../../commit/fe8dbe28fb7692aa6abdaa1d871cc986ebef8f14)) * Print unpacked size with command `eask info` ([`82cd36c`](../../commit/82cd36c34a4b978fe76b7f9b2099ea7929d31a60)) * Add new command `eask buttercup` ([`4ef7b3a`](../../commit/4ef7b3a054971aafc57d0060f62cf70b4126c97c)) * Add new command `eask indent` ([`99ec942`](../../commit/99ec942878452ae1c27f027f4a577eefb70a0d92)) * Add new command `eask create` ([`94b1075`](../../commit/94b1075f919d53c0741c61ebea42a0fbed3d1ccf)) * Add new command `eask declare` ([`04b3b51`](../../commit/04b3b51a6a07d3a84611b6082ca46f2aafece7bd)) * Add new command `eask regexps` ([`9121e1c`](../../commit/9121e1c37b2640a21cdd7abd1d0a2f51ac1768dd)) ## 0.5.x > Released Apr 18, 2022 * Unified dependencies list with data structure of list, and no longer have string ([`9f09f93`](../../commit/9f09f93ec866e32d14dd2f6c53ee8fde6cd4b986)) * Add new command `eask refresh` ([`5423d84`](../../commit/5423d84a59c88d52c2279f27a189573eb525a82b)) * Move help manual to it's folder ([`4036319`](../../commit/4036319d3d40ea37270103adbfb102dd0d6b24d5)) * Move command files by category ([`2b405c5`](../../commit/2b405c5cc65eb9a3099b2bc4b6b3b41395d7ba0f)) * Add new command `eask search` ([`840646c`](../../commit/840646c1b91fcbed689d33cb6890bf482b9b1913)) * Restrict `package` definition in `Eask`-file ([`ba6ed68`](../../commit/ba6ed6853c98d19d88971af745ad9a8b2d794ae5)) * Add new command `eask ert-runner` ([`8647d37`](../../commit/8647d37c80c5349d8b3b3b17b8570a91bb91339c)) * Add new command `eask ert` ([`29c5722`](../../commit/29c5722fa5b8fea8add30d6de0169166cfd7c17f)) * Handle error `Failed to download ‘xxx’ archive` ([`29887c8`](../../commit/29887c80c33b9f909151b465cb99160160cc96c3)) * Handle Emacs version while installing packages ([`8cafd4f`](../../commit/8cafd4f2d34829da5832241cf105753658019abf)) * Make error/warning log check compatible to older Emacs version ([`397d374`](../../commit/397d3740f1eee5f8e078116d323eeca9479f5114)) * DSL `files` support multiple definition ([`3948a1d`](../../commit/3948a1d2366b82eb0a7e11bf1131867881c83ad7)) * Colorized ERT tests result ([`1b55b51`](../../commit/1b55b51eb4fbe5e602bd7aed8d8e05e372a0cd95)) * Simulate batch-mode ([`d67098c`](../../commit/d67098c10a683cd05893d8fda46105229f287394)) * Add new command `eask check-eask` ([`621fb8e`](../../commit/621fb8eefe41f513c9fe090daaec24c1ee9083e6)) * Use built-in `pkg-file` generate function for command `eask pkg-file` ([`4aa327b`](../../commit/4aa327bf43b84dfed128f8898b1c0885afc1edf9)) ## 0.4.x > Released Apr 05, 2022 * Add Eask file keyword `exec-paths` ([`5334bc5`](../../commit/5334bc5e16e8d52a9e6a7f23781f2b42d333d11c)) * Remove Eask file keyword `load-path` ([`a72fdac`](../../commit/a72fdacece54aa7acebfea3265bf9a0eace41778)) * Add new command `eask checkdoc` ([`c10ccb9`](../../commit/c10ccb908f8af5bc45094efff17364eecf0b6fbc)) * Fix install dependencies when calling package init ([`e9be0bc`](../../commit/e9be0bc42fea6620572c70aeaf0fc684e7a5ce5c)) * Add color to the output ([`afa74da`](../../commit/afa74da680150fb8f9d50187adf1922b21f83fc9)) * Add new option `--allow-error` ([`c9c4cf2`](../../commit/c9c4cf24a6d42633f8c725385c7e4910774075ff)) * Add new command `eask install-deps` ([`955a362`](../../commit/955a36231fa968e1fdedd29ae9c818385d21f93e)) * Add new option `--insecure` ([`9c41e5c`](../../commit/9c41e5ce65b9e92f1e807ed43ba735bfa7cac7e2)) * Add new command `eask upgrade-eask` ([`e1f21fe`](../../commit/e1f21fec84a5461c0c027a4560bfdb84428e6f99)) * Add new command `eask locate` ([`cef319d`](../../commit/cef319d0820932cf619bd8836a7b0f7b36742746)) * Colorized compile log for `eask compile` command ([`cfc3105`](../../commit/cfc31054d1ce4e077b4b501bc38adc41660c4cab)) * No longer require package after installation! ([`9175c8b`](../../commit/9175c8b5cbdf97b6410cd35b438db406f4f841be)) * Hide script name in commands list ([`7ec2967`](../../commit/7ec2967152e11f1bbbd9a48d94835a7a8cd5aa1f)) * Integrate SPEC for `depends-on` DSL ([`4250ea8`](../../commit/4250ea837e41d1357b2ce98e4902c38e73adb59e)) * Move help manual to individual files ([`4036319`](../../commit/4036319d3d40ea37270103adbfb102dd0d6b24d5)) ## 0.3.x > Released Mar 30, 2022 * Ensure use `throw` operation to trigger CI ([`0253edf`](../../commit/0253edff816a85868daf708289872542d49af729)) * Fix extra concatenation args ([`458d551`](../../commit/458d551d2e1981407929ada1ad8b4b4b430cf526)) * Use inherit options for `spawn` process ([`aaf4ace`](../../commit/aaf4ace0603905574ccbe1c900c057e31988bdee)) * Add command `eask package [dest]` ([`a304cc2`](../../commit/a304cc2342c4cd85a59d38fb61709543d3d39fca)) * Add `shmelpa` source ([`578ea2c`](../../commit/578ea2cc0b11c145c33833b87a01e938aca48e0e)) * Improve output message ([`9aa9ba5`](../../commit/9aa9ba5977779fe837f084ff54666c6104361e07)) * Disable backup file by default ([`3be0010`](../../commit/3be0010d3fe19ea7cd1d5fd64931532c98f6267f)) * Fix line endings on Windows system ([`b7b25da`](../../commit/b7b25da75f794e1bd1c1f6b74a9d4cd6a83e3435)) * Fix option that accept arguments ([`979d87e`](../../commit/979d87ef6953f0f4a9403db4bb244c87987ea478)) * Add verbosity level and timestamps ([`9de0bb4`](../../commit/9de0bb4155911c5399d9d2ddb0cbff9dfc25f88f)) * Add new command `eask concat` ([`2c85287`](../../commit/2c85287e4a24d667d99185c05de6f714bcc669a0)) * Add new options `--timestamps` and `--no-timestamps` ([`3f167d6`](../../commit/3f167d64e3115eea2580708f7eff2becce246537)) * Add new option `--strict` ([`6d59d98`](../../commit/6d59d98999f2bd46e66e2a6d03064ee56678a591)) * Add new options `--log-level` and `--no-log-level` ([`e5e0367`](../../commit/e5e03679db496ce77d1ba4adde1fc6b42d931ba9)) * Improve output messages, more! ## 0.2.x > Released Mar 26, 2022 * Fix install command ([`286fa96`](../../commit/286fa96475358bfee29645c35192646ac1762724)) * Fix load path for `_prepare.el` script ([`87946d5`](../../commit/87946d5d0b792dd6727f2afa8e4bdcd8e395d67a)) * Add command `eask compile` ([`f242227`](../../commit/f2422276748cdba2dd74b33fcf741f491dbc65df)) * Add Eask file options `source-priority` * Add Eask file options `load-path` * Add Eask file options `load-paths` * Add command `eask list` * Add command `eask info` * Add command `eask files` * Add command `eask clean-elc` * Add command `eask load-path` * Show help on command fails ([`cf17c08`](../../commit/cf17c081bfd339a09be8b9e43723a739e4bb3f58)) * Add command `eask lint` ([`4157f43`](../../commit/4157f4310d639612fe5cab1d5b2d75747dde2311)) * Add hooks for all commands, including the master one. * Command `compile`, and `lint` accept multiple arguments, `files` ([`b06f113`](../../commit/b06f1139fbcc404f4148948e3f3429d23773a6c0)) * Add command `eask init` ([`846f897`](../../commit/846f8972c597cc1427bc9990fa7e452a9a963bb9)) * Add command `eask exec` ([`83cc11d`](../../commit/83cc11d5b9aa4f818a11f53cb29a6a076f05739b)) * Add command `eask package-directory` ([`e3098c8`](../../commit/e3098c8b0816a9f1a1fa953fa46f140e60c29bb2)) * Add command `eask path` ([`3b37957`](../../commit/3b37957c53d5e662258014da82e81a3550bcb085)) * Add command `eask eval` ([`a3d8cd5`](../../commit/a3d8cd5a3d28aabee2aec3950f8e74fc299b75dd)) * Add command `eask list-all` ([`5817606`](../../commit/5817606f85dbb88253b6eb30cf072e2a8cd4f5c2)) * Add command `eask autoloads` ([`3f7c989`](../../commit/3f7c98954aa7ffb5dce4c424d380d7ef4af66a7c)) * Add command `eask pkg-file` ([`e267b5a`](../../commit/e267b5a26987f1e1361864280cf8781ee58200c2)) * Add command `eask outdated` ([`eb03a2b`](../../commit/eb03a2b1cecd20620746edc4fe6719ed7a4c0b59)) * Command `load-path` and `exec-path` can now accept `-g` option. ([`e73bff7`](../../commit/e73bff70b21290c2b79a7c13091701f712ce6d0b)) * Add proxy global options ([`8e3a913`](../../commit/8e3a9130805341bd4c1ce9ccb8e4bf0a42e16ee8)) * Accept `Eask` file from default `~/.emacs.d/` directory ([`044ce93`](../../commit/044ce9327a60cad8adb4bc34141b2f8e8ecd2b45)) * Use `spawn` instead of `exec`, so new the terminal will be updated immediately ([`e1790a8`](../../commit/e1790a833ba2422d3d171523a2670f66f0485173)) * Done basic error handling with exit code at the end of executions ([`e5afb70`](../../commit/e5afb70f6bbdf0424681949dd10ebd02b9fc7a25)) ## 0.1.x > Released Mar 15, 2022 * Very early release. ================================================ FILE: Dockerfile ================================================ FROM nixos/nix # Install Emacs RUN nix-env -iA nixpkgs.emacs # Install Node.JS RUN nix-env -iA nixpkgs.nodejs # Move the whole project in. WORKDIR /cli COPY . . # Install package dependencies. WORKDIR /cli RUN npm install --include=dev # Expose it. ENV PATH="${PATH}:/cli/bin" ================================================ FILE: Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "cli" "0.12.10" "A set of command-line tools to build Emacs packages") (website-url "https://github.com/emacs-eask/cli") (keywords "emacs" "package" "management" "cli") (script "test" "echo \"Error: no test specified\" && exit 1") (script "install" "npm install") (script "setup-doc" "cd ./docs/themes/geekdoc/ && npm install && npm run build") (script "serve-doc" "cd ./docs/ && hugo server") (files "eask" "bin" "cmds/**/*.js" "lisp/**/*.el" "src") (depends-on "emacs" "26.1") ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: Makefile ================================================ EMACS ?= emacs EASK ?= eask .PHONY: test-redefine # ## Development test-redefine: @echo "Test redefine..." $(EASK) concat $(EASK) load ./test/development/compile/test-redefine.el test-compat: @echo "Test compatibility..." $(EASK) load ./test/development/compat.el --allow-error color: ./test/color/run.sh error: ./test/error/run.sh ================================================ FILE: PKGBUILD ================================================ pkgname='eask' pkgver=$(git tag --sort=-creatordate | head -n1).$(git rev-parse --short HEAD) pkgrel=1 pkgdesc='CLI for building, running, testing, and managing your Emacs Lisp dependencies' arch=('x86_64') makedepends=('npm') options=('!strip') # node puts scripts into debug section, so we can't strip it url='https://github.com/emacs-eask/cli' license=('GPL-3.0') prepare() { npm i } build() { npm run pkg-linux-x64 } package() { mkdir -p "$pkgdir/usr/bin" # ATM eask only works when `lisp` dir is installed together with the binary cp -r "${startdir}/lisp" "${startdir}/dist/eask" "$pkgdir/usr/bin" } ================================================ FILE: README.md ================================================ # Eask > CLI for building, running, testing, and managing your Emacs Lisp dependencies [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-green.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Emacs Version](https://img.shields.io/badge/Emacs-26.1+-7F5AB6.svg?logo=gnu%20emacs&logoColor=white)](https://www.gnu.org/software/emacs/download.html) [![Release](https://img.shields.io/github/release/emacs-eask/cli.svg?logo=github)](https://github.com/emacs-eask/cli/releases/latest) [![Discord](https://img.shields.io/discord/1131434607213023262?label=Discord&logo=discord&logoColor=white&color=7289DA)](https://discord.gg/E9zzjWGfFD) Eask was originally designed as a package development tool for Elisp projects. However, it has since expanded to support a wide range of Emacs Lisp tasks. It can now be used in three major ways: 1. As a development tool for Elisp packages. 2. For managing dependencies in your Emacs configuration. 3. To run Elisp programs for a variety of purposes (essentially functioning as a runtime). With these capabilities in mind, what sets Eask apart from other build tools like [Cask][], [makem.sh][], and [Eldev][]? Great question! Eask has evolved beyond just a build tool—it serves multiple purposes! Here’s what Eask aims to be: - **Consistent**: Provides a reliable sandboxing environment across all systems. - **Versatile**: Includes commonly used Emacs commands like `byte-compilation`, `checkdoc`, and more. - **Robust**: Delivers useful results even when user errors occur. - **Lightweight**: Runs on any platform without dependencies. *📝 P.S. See [Why Eask?](https://emacs-eask.github.io/Getting-Started/Introduction/#-why-eask) for more detailed information.* ## 🔗 Links > 💡 [`node`][node] is not required to use Eask! - [Documentation](https://emacs-eask.github.io/) - [Installation](https://emacs-eask.github.io/Getting-Started/Install-Eask/) - [Command-line interface](https://emacs-eask.github.io/Getting-Started/Commands-and-options/) - [Examples](https://emacs-eask.github.io/Examples/Real-project-examples/) - [FAQ](https://emacs-eask.github.io/FAQ/) ## 🧪 Testing We have incorporated a range of tests to ensure Eask remains stable throughout its release cycle. ###### Documentation | Description | Done | Status | |----------------------------------------|------|-------------------------------------------------------------------------------------------------------------------------------------------------| | Keep the documentation page up to date | ✔ | [![Docs](https://github.com/emacs-eask/cli/actions/workflows/docs.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/docs.yml) | ###### Development | Description | Done | Status | |--------------------------------------------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------| | Compile source and check redefined | ✔ | [![Compile](https://github.com/emacs-eask/cli/actions/workflows/compile.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/compile.yml) | | Compatibility check for each Emacs version | ✔ | [![Compat](https://github.com/emacs-eask/cli/actions/workflows/compat.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/compat.yml) | | Build executables | ✔ | [![Build](https://github.com/emacs-eask/cli/actions/workflows/build.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/build.yml) | ###### Commands | Description | Done | Status | |----------------------------------------------|------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Test commands in global (`~/.eask/`) mode | ✔ | [![Global](https://github.com/emacs-eask/cli/actions/workflows/global.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/global.yml) | | Test commands in config (`~/.emacs.d/`) mode | ✔ | [![Confg](https://github.com/emacs-eask/cli/actions/workflows/config.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/config.yml) | | Test commands in development (`./`) mode | ✔ | [![Local](https://github.com/emacs-eask/cli/actions/workflows/local.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/local.yml) | | Test install packages | ✔ | [![Install](https://github.com/emacs-eask/cli/actions/workflows/install.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/install.yml) | | Test link packages | ✔ | [![Link](https://github.com/emacs-eask/cli/actions/workflows/link.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/link.yml) | | Test `analyze` command / `Eask`-file checker | ✔ | [![Analyze](https://github.com/emacs-eask/cli/actions/workflows/analyze.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/analyze.yml) | | Test `docker` command | ✔ | [![Docker](https://github.com/emacs-eask/cli/actions/workflows/docker.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/docker.yml) | | Test `exec` command | ✔ | [![Exec](https://github.com/emacs-eask/cli/actions/workflows/exec.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/exec.yml) | | Test `emacs` command | ✔ | [![Emacs](https://github.com/emacs-eask/cli/actions/workflows/emacs.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/emacs.yml) | | Test search packages | ✔ | [![Search](https://github.com/emacs-eask/cli/actions/workflows/search.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/search.yml) | | Test upgrade and check outdated packages | ✔ | [![Outdated_Upgrade](https://github.com/emacs-eask/cli/actions/workflows/outdated_upgrade.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/outdated_upgrade.yml) | | Test `upgrade-eask` command | ✔ | [![Upgrade Eask](https://github.com/emacs-eask/cli/actions/workflows/upgrade-eask.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/upgrade-eask.yml) | ###### Options | Description | Done | Status | |----------------------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------| | Test option switches | ✔ | [![Options](https://github.com/emacs-eask/cli/actions/workflows/options.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/options.yml) | ###### Test | Description | Done | Status | |---------------------------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Test `ert` command | ✔ | [![Test ert](https://github.com/emacs-eask/cli/actions/workflows/test_ert.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/test_ert.yml) | | Test `ert-runner` command | ✔ | [![Test ert-runner](https://github.com/emacs-eask/cli/actions/workflows/test_ert-runner.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/test_ert-runner.yml) | | Test `buttercup` command | ✔ | [![Test buttercup](https://github.com/emacs-eask/cli/actions/workflows/test_buttercup.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/test_buttercup.yml) | | Test `ecukes` command | ✔ | [![Test ecukes](https://github.com/emacs-eask/cli/actions/workflows/test_ecukes.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/test_ecukes.yml) | ###### Others | Description | Done | Status | |-------------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Webinstall | ✔ | [![Webinstall](https://github.com/emacs-eask/cli/actions/workflows/webinstall.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/webinstall.yml) | | Exit Status | ✔ | [![Exit Status](https://github.com/emacs-eask/cli/actions/workflows/exit_status.yml/badge.svg)](https://github.com/emacs-eask/cli/actions/workflows/exit_status.yml) | ## ⚜️ License This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . See [`LICENSE`](./LICENSE) for details. [Cask]: https://github.com/cask/cask [makem.sh]: https://github.com/alphapapa/makem.sh [Eldev]: https://github.com/doublep/eldev [node]: https://nodejs.org/ ================================================ FILE: bin/eask ================================================ #!/usr/bin/env bash # Copyright (C) 2022-2026 the Eask authors. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . function _get_current_directory() { if dirname "$(readlink -f "${0}")" &>/dev/null then CDIR="$(cd "$(dirname "$(readlink -f "${0}")")" && pwd)" elif realpath -e -L "${0}" &>/dev/null then CDIR="$(realpath -e -L "${0}")" CDIR="${CDIR%/eask.js}" fi } CDIR="$(pwd)" _get_current_directory node "$CDIR/../eask.js" "$@" ================================================ FILE: bin/eask.bat ================================================ @echo off :: Copyright (C) 2022-2026 the Eask authors. :: This program is free software; you can redistribute it and/or modify :: it under the terms of the GNU General Public License as published by :: the Free Software Foundation; either version 3, or (at your option) :: any later version. :: This program is distributed in the hope that it will be useful, :: but WITHOUT ANY WARRANTY; without even the implied warranty of :: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the :: GNU General Public License for more details. :: You should have received a copy of the GNU General Public License :: along with this program. If not, see . node "%~dp0/../eask.js" %* ================================================ FILE: cmds/clean/all.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['all', 'everything']; exports.desc = 'Do all cleaning tasks'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/all'); }; ================================================ FILE: cmds/clean/autoloads.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['autoloads']; exports.desc = 'Remove the generated autoloads file'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/autoloads', argv.dest); }; ================================================ FILE: cmds/clean/dist.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['dist [destination]', 'distribution [destination]']; exports.desc = 'Delete the dist directory where the build output is stored'; exports.builder = yargs => yargs .positional( 'destination', { description: 'destination path/folder to clean up', alias: 'dest', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/dist', argv.dest); }; ================================================ FILE: cmds/clean/elc.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['elc']; exports.desc = 'Remove byte compiled files generated by eask compile'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/elc'); }; ================================================ FILE: cmds/clean/log-file.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['log-file']; exports.desc = 'Remove all generated log files'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/log-file'); }; ================================================ FILE: cmds/clean/pkg-file.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['pkg-file']; exports.desc = 'Remove the generated pkg-file'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/pkg-file'); }; ================================================ FILE: cmds/clean/workspace.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['workspace', '.eask']; exports.desc = 'Clean up the .eask directory'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'clean/workspace'); }; ================================================ FILE: cmds/core/analyze.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['analyze [files..]']; exports.desc = 'Run Eask checker'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify Eask-files for checker to lint', type: 'array', }) .options({ 'output': { description: 'Output result to a file', alias: 'o', type: 'string', group: TITLE_CMD_OPTION, }, 'json': { description: 'Output lint result in JSON format', type: 'boolean', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/analyze' , argv.files , UTIL.def_flag(argv.json, '--json') , UTIL.def_flag(argv.output, '--output', argv.output)); }; ================================================ FILE: cmds/core/archives.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['archives', 'sources']; exports.desc = 'List out all package archives'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/archives'); }; ================================================ FILE: cmds/core/bump.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['bump [levels..]']; exports.desc = UTIL.hide_cmd('Bump version for your project'); exports.builder = yargs => yargs .positional( '[levels..]', { description: "version level to bump; accept `major', `minor' or `patch'", type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/bump' , argv.levels); }; ================================================ FILE: cmds/core/cat.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['cat ', 'type ']; exports.desc = UTIL.hide_cmd('View filename(s)'); exports.builder = yargs => yargs .positional( '', { description: 'patterns you want to search (wildcard)', type: 'array', }) .options({ 'number': { description: 'show with line numbers', requiresArg: false, alias: 'n', type: 'boolean', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/cat' , argv.patterns , UTIL.def_flag(argv.number, '--number')); }; ================================================ FILE: cmds/core/clean.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['clean ']; exports.desc = 'Delete various files produced during building'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask clean [options..]`) .commandDir('../clean/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the cleaning task', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/compile.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['compile [names..]']; exports.desc = "Byte-compile `.el' files"; exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify files to byte-compile', type: 'array', }) .options({ 'clean': { description: 'clean byte-compile files individually', type: 'boolean', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/compile' , argv.names , UTIL.def_flag(argv.clean, '--clean')); }; ================================================ FILE: cmds/core/concat.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['concat [names..]', 'concatenate [names..]']; exports.desc = UTIL.hide_cmd('Concatenate elisp files'); exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify files to concatenate', type: 'array', }) .options({ 'destination': { description: 'optional output destination', requiresArg: true, alias: 'dest', type: 'string', group: TITLE_CMD_OPTION, }, 'output': { description: 'Output result to a file', alias: 'o', type: 'string', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/concat' , argv.names , UTIL.def_flag(argv.dest, '--dest', argv.dest) , UTIL.def_flag(argv.output, '--output', argv.output)); }; ================================================ FILE: cmds/core/create.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['create ']; exports.desc = 'Create a new elisp project'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask create [options..]`) .commandDir('../create/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the creation', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/docker.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const path = require('path'); const child_process = require("child_process"); exports.command = ['docker [args..]']; exports.desc = 'Launch specified Emacs version in a Docker container'; exports.builder = async (yargs) => { yargs.help(false); yargs.version(false); yargs.getOptions().narg = []; yargs.strict(false); yargs.positional('', { description: 'Emacs version to test', type: 'string', }); }; exports.handler = async (argv) => { if (!UTIL.which('docker')) { console.warn("Docker is not installed (cannot find `docker' executable)"); return; } let project_dir = convert_path(process.cwd()); if (!project_dir.startsWith('/')) { // XXX: Ensure compatible to Unix path! project_dir = '/' + project_dir; } let container_dir = '/' + path.basename(project_dir); let container_arg = project_dir + ':' + container_dir; // Find out the image name. let image = argv.version; if (valid_version_string(argv.version)) image = 'silex/emacs:' + argv.version + '-eask'; let default_cmd = ['docker', 'run', '--rm', '-v', container_arg, '-w', container_dir, image,]; let rest = process.argv.slice(4); // If no argument; we enter the container directly! if (rest.length == 0) default_cmd.splice(2, 0, '-it'); else default_cmd.push('eask'); let cmd = default_cmd.concat(rest); let proc = child_process.spawn(UTIL.cli_args(cmd), { stdio: 'inherit', shell: true }); proc.on('close', function (code) { if (code == 0) return; process.exit(code); }); }; /** * Convert path so docker can recognize! * @param { String } path - Path to convert. */ function convert_path(path) { return UTIL.slash(path).replaceAll(':', ''); } /** * Return true when string is a valid version string. * @param { String } str - The version string. */ function valid_version_string(str) { const ver_regex = /^\d+(\.\d+)*(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/; return ver_regex.test(str); } ================================================ FILE: cmds/core/docs.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['docs ', 'doc ']; exports.desc = 'Build documentation'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask docs [options..]`) .commandDir('../docs/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the generator', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/emacs.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const child_process = require("child_process"); exports.command = ['emacs [args..]']; exports.desc = 'Execute emacs with the appropriate environment'; exports.builder = async (yargs) => { yargs.help(false); yargs.version(false); yargs.getOptions().narg = []; yargs.strict(false); }; exports.handler = async (argv) => { let s_path = UTIL.el_script('core/emacs'); let default_cmd = [EASK_EMACS, '-Q', '-l', s_path]; let rest = UTIL.take_after(process.argv, EASK_EMACS); let cmd = default_cmd.concat(rest); UTIL.setup_env(); let proc = child_process.spawn(UTIL.cli_args(cmd), { stdio: 'inherit', shell: true }); proc.on('close', function (code) { if (code == 0) return; process.exit(code); }); }; ================================================ FILE: cmds/core/eval.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const fs = require('fs'); const child_process = require("child_process"); exports.command = ['eval [form]']; exports.desc = 'Evaluate lisp form with a proper PATH'; exports.builder = yargs => yargs .positional( '[form]', { description: 'lisp form', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/eval', argv.form); if (!fs.existsSync(EASK_HOMEDIR)) { return; } let epf = EASK_HOMEDIR + 'exec-path'; let lpf = EASK_HOMEDIR + 'load-path'; if (!fs.existsSync(epf) || !fs.existsSync(lpf)) { return; } process.env.PATH = fs.readFileSync(epf, 'utf8'); process.env.EMACSLOADPATH = fs.readFileSync(lpf, 'utf8');; let expressions = process.argv.slice(3); // XXX: Just replace `form` property, this is combined expression argv.form = expressions.join(' '); let cmd = ['emacs', '--batch', '--eval', argv.form]; let proc = child_process.spawn(UTIL.cli_args(cmd), { stdio: 'inherit', shell: true }); proc.on('close', function (code) { if (code == 0) return; process.exit(code); }); }; ================================================ FILE: cmds/core/exec-path.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['path [patterns..]', 'exec-path [patterns..]']; exports.desc = 'Print the PATH (exec-path) from workspace'; exports.builder = yargs => yargs .positional( '[patterns..]', { description: 'patterns you want to search (regex)', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/exec-path', argv.patterns); }; ================================================ FILE: cmds/core/exec.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const fs = require('fs'); const child_process = require("child_process"); exports.command = ['exec [args..]']; exports.desc = 'Execute command with correct environment PATH set up'; exports.builder = async (yargs) => { yargs.help(false); yargs.version(false); yargs.getOptions().narg = []; yargs.strict(false); }; exports.handler = async (argv) => { let cmd = UTIL.take_after(process.argv, 'exec'); await UTIL.e_call(argv, 'core/exec', '--', cmd); if (cmd.length == 0) { return; } if (!fs.existsSync(EASK_HOMEDIR)) { return; } let epf = EASK_HOMEDIR + 'exec-path'; let lpf = EASK_HOMEDIR + 'load-path'; if (!fs.existsSync(epf) || !fs.existsSync(lpf)) { return; } process.env.PATH = fs.readFileSync(epf, 'utf8'); process.env.EMACSLOADPATH = fs.readFileSync(lpf, 'utf8'); let proc = child_process.spawn(UTIL.cli_args(cmd), { stdio: 'inherit', shell: true }); proc.on('close', function (code) { if (code == 0) return; process.exit(code); }); }; ================================================ FILE: cmds/core/files.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['files [patterns..]']; exports.desc = 'Print all package files'; exports.builder = yargs => yargs .positional( '[patterns..]', { description: 'patterns you want to search (wildcard)', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/files', argv.patterns); }; ================================================ FILE: cmds/core/format.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['format ', 'fmt ']; exports.desc = 'Run formatters'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask format [options..]`) .commandDir('../format/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the formatter', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/generate.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['generate ']; exports.desc = 'Generate files that are used for the development'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask generate [options..]`) .commandDir('../generate/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the file', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/info.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['info']; exports.desc = 'Display information about the current package'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/info'); }; ================================================ FILE: cmds/core/init.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['init [files..]']; exports.desc = 'Initialize project to use Eask'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files to use with `--from` flag', type: 'array', }) .options({ 'from': { description: 'build from an existing package', requiresArg: true, type: 'string', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { if (argv.from) { switch (argv.from) { case 'cask': case 'eldev': case 'keg': case 'source': await UTIL.e_call(argv, 'init/' + argv.from , UTIL.def_flag(argv.from, '--from', argv.from) , argv.files); break; default: console.warn(`Invalid argument, from: ${argv.from}`); break; } } else { await UTIL.e_call(argv, 'core/init'); } }; ================================================ FILE: cmds/core/install-deps.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['install-deps', 'install-dependencies', 'prepare']; exports.desc = 'Automatically install package dependencies'; exports.builder = yargs => yargs .options({ 'development': { description: 'also install development dependencies', alias: 'dev', type: 'boolean', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/install-deps' , UTIL.def_flag(argv.development, '--dev')); }; ================================================ FILE: cmds/core/install-file.js ================================================ /** * Copyright (C) 2025-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['install-file [files..]']; exports.desc = 'Install packages from files, .tar files, or directories'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files to install as packages', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/install-file', argv.files); }; ================================================ FILE: cmds/core/install-vc.js ================================================ /** * Copyright (C) 2025-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['install-vc [specs..]']; exports.desc = 'Fetch and install packages directly via version control'; exports.builder = yargs => yargs .positional( '[specs..]', { description: 'vc specification to install as packages', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/install-vc', argv.specs); }; ================================================ FILE: cmds/core/install.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['install [names..]']; exports.desc = 'Install packages from archives or install from the workspace'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'packages to install', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/install', argv.names); }; ================================================ FILE: cmds/core/keywords.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['keywords']; exports.desc = 'Display the available keywords for use in the header section'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/keywords'); }; ================================================ FILE: cmds/core/link.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['link ']; exports.desc = 'Manage links'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask link [options..]`) .commandDir('../link/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of link action', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/lint.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['lint ']; exports.desc = 'Run linters'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask lint [options..]`) .commandDir('../lint/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the linter', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/list.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['list']; exports.desc = 'List all installed packages in dependency tree form'; exports.builder = yargs => yargs .options({ 'depth': { description: 'dependency depth level to print', requiresArg: true, type: 'number', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/list' , UTIL.def_flag(argv.depth, '--depth', argv.depth)); }; ================================================ FILE: cmds/core/load-path.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['load-path [patterns..]']; exports.desc = 'Print the load-path from workspace'; exports.builder = yargs => yargs .positional( '[patterns..]', { description: 'patterns you want to search (regex)', requiresArg: false, type: 'array', group: TITLE_CMD_OPTION, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/load-path', argv.patterns); }; ================================================ FILE: cmds/core/load.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['load [files..]']; exports.desc = 'Load elisp files'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files to load', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/load', argv.files); }; ================================================ FILE: cmds/core/loc.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['loc [files..]']; exports.desc = UTIL.hide_cmd('Print LOC information'); exports.builder = yargs => yargs .positional( '[files..]', { description: 'files to print LOC information', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/loc', argv.files); }; ================================================ FILE: cmds/core/outdated.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['outdated']; exports.desc = 'Show all outdated dependencies'; exports.builder = yargs => yargs .options({ 'depth': { description: 'dependency depth level to print', requiresArg: true, type: 'number', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/outdated'); }; ================================================ FILE: cmds/core/package-directory.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['package-directory']; exports.desc = 'Print the path to package directory'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/package-directory'); }; ================================================ FILE: cmds/core/package.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['package [destination]', 'pack [destination]']; exports.desc = 'Build a package artifact, and put it into the given destination'; exports.builder = yargs => yargs .positional( 'destination', { description: 'destination path/folder', alias: 'dest', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/package', argv.dest); }; ================================================ FILE: cmds/core/recipe.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['recipe']; exports.desc = 'Suggest a recipe format'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/recipe'); }; ================================================ FILE: cmds/core/recompile.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['recompile [names..]']; exports.desc = "Byte-recompile `.el' files"; exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify files to byte-compile', type: 'array', }) .options({ 'clean': { description: 'clean byte-recompile files individually', type: 'boolean', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/recompile' , argv.names , UTIL.def_flag(argv.clean, '--clean')); }; ================================================ FILE: cmds/core/refresh.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['refresh']; exports.desc = 'Download descriptions of all configured package archives'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/refresh'); }; ================================================ FILE: cmds/core/reinstall.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['reinstall [names..]']; exports.desc = 'Reinstall packages from archives'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'packages to reinstall', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/reinstall', argv.names); }; ================================================ FILE: cmds/core/repl.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['repl', 'ielm']; exports.desc = UTIL.hide_cmd('Start an Elisp REPL'); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/repl'); }; ================================================ FILE: cmds/core/run.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['run ']; exports.desc = 'Run custom tasks'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask run [options..]`) .commandDir('../run/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the execution', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/search.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['search [queries..]']; exports.desc = 'Search packages from archives'; exports.builder = yargs => yargs .positional( '[queries..]', { description: 'queries to search packages', type: 'array', }) .options({ 'depth': { description: 'dependency depth level to print', requiresArg: true, type: 'number', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/search', argv.queries , UTIL.def_flag(argv.depth, '--depth', argv.depth)); }; ================================================ FILE: cmds/core/source.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['source ']; exports.desc = UTIL.hide_cmd('Manage sources'); exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask source [options..]`) .commandDir('../source/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the control', }); } } exports.handler = async (argv) => {}; ================================================ FILE: cmds/core/status.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['status']; exports.desc = 'Show the workspace status'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/status'); }; ================================================ FILE: cmds/core/test.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['test ']; exports.desc = "Run regression/unit tests"; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask test [options..]`) .commandDir('../test/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 1) { yargs.positional( '', { description: 'type of the test framework', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/core/uninstall.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['uninstall [names..]', 'delete [names..]']; exports.desc = 'Uninstall packages from archives'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'packages to uninstall', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/uninstall', argv.names); }; ================================================ FILE: cmds/core/upgrade.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['upgrade [names..]']; exports.desc = 'Upgrade packages from archives'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'packages to upgrade', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/upgrade', argv.names); }; ================================================ FILE: cmds/create/el-project.js ================================================ /** * Copyright (C) 2025-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['el-project']; exports.desc = 'Create a new project with `el-project`'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'create/el-project'); }; ================================================ FILE: cmds/create/elpa.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const child_process = require('child_process'); exports.command = ['elpa ']; exports.desc = 'Create a new ELPA using github-elpa'; exports.builder = yargs => yargs .positional( '', { description: 'new ELPA name', type: 'string', }); const TEMPLATE_URL = 'https://github.com/emacs-eask/template-elpa'; exports.handler = async (argv) => { const project_name = argv.name; let proc = child_process.spawn('git', ['clone', TEMPLATE_URL, project_name], { stdio: 'inherit' }); // You would just need to register the error event, or else it can't print // the help instruction below. proc.on('error', function () { }); proc.on('close', function (code) { if (code == 0) { console.warn('✓ Done cloning the ELPA template'); console.warn(''); process.chdir(project_name); _cloned(argv); return; } // Help instruction here! console.warn('✗ Error while cloning template project'); console.warn(''); console.warn(' [1] Make sure you have git installed and has the right permission'); console.warn(` [2] Failed because of the target directory isn't empty`); console.warn(''); process.stderr.write(`Visit https://emacs-eask.github.io/ for quickstart guide and full documentation.`); }); } /* Operations after _cloned */ async function _cloned(argv) { console.warn('Initialize the Eask-file for your project...'); await UTIL.e_call(argv, 'core/init'); await UTIL.e_call(argv, 'create/elpa'); } ================================================ FILE: cmds/create/package.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const child_process = require('child_process'); exports.command = ['package ', 'pkg ']; exports.desc = 'Create a new package'; exports.builder = yargs => yargs .positional( '', { description: 'new package name', type: 'string', }); const TEMPLATE_URL = 'https://github.com/emacs-eask/template-elisp'; exports.handler = async (argv) => { const project_name = argv.name; let proc = child_process.spawn('git', ['clone', TEMPLATE_URL, project_name], { stdio: 'inherit' }); // You would just need to register the error event, or else it can't print // the help instruction below. proc.on('error', function () { }); proc.on('close', function (code) { if (code == 0) { console.warn('✓ Done cloning the elisp package template'); console.warn(''); process.chdir(project_name); _cloned(argv); return; } // Help instruction here! console.warn('✗ Error while cloning template project'); console.warn(''); console.warn(' [1] Make sure you have git installed and has the right permission'); process.stderr.write(` [2] Failed because of the target directory isn't empty`); }); }; /* Operations after _cloned */ async function _cloned(argv) { console.warn('Initialize the Eask-file for your project...'); await UTIL.e_call(argv, 'core/init'); await UTIL.e_call(argv, 'create/package'); } ================================================ FILE: cmds/docs/el2org.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['el2org [names..]', ]; exports.desc = 'Build documentation with el2org'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify source files to scan', type: 'array', }) .options({ 'destination': { description: 'optional output destination', requiresArg: true, alias: 'dest', type: 'string', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'docs/el2org' , argv.names , UTIL.def_flag(argv.dest, '--dest', argv.dest)); }; ================================================ FILE: cmds/format/elfmt.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['elfmt [files..]']; exports.desc = 'Reformat Elisp source with elfmt'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify files to do elfmt', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'format/elfmt', argv.files); }; ================================================ FILE: cmds/format/elisp-autofmt.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['elisp-autofmt [files..]']; exports.desc = 'Reformat Elisp source with elisp-autofmt'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify files to do elisp-autofmt', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'format/elisp-autofmt', argv.files); }; ================================================ FILE: cmds/generate/autoloads.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['autoloads']; exports.desc = 'Generate the autoloads file'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/autoloads'); }; ================================================ FILE: cmds/generate/ignore.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ignore ']; exports.desc = 'Generate an ignore file using .gitignore templates'; exports.builder = yargs => yargs .positional( '', { description: 'name of the ignore template', type: 'string', }) .options({ 'output': { description: 'output result to a file; the default is `.gitignore`', alias: 'o', type: 'string', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/ignore' , argv.name , UTIL.def_flag(argv.output, '--output', argv.output)); }; ================================================ FILE: cmds/generate/license.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['license ']; exports.desc = 'Generate the LICENSE file'; exports.builder = yargs => yargs .positional( '', { description: 'name of the license', type: 'string', }) .options({ 'output': { description: 'output result to a file; the default is `LICENSE`', alias: 'o', type: 'string', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/license' , argv.name , UTIL.def_flag(argv.output, '--output', argv.output)); }; ================================================ FILE: cmds/generate/pkg-file.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['pkg-file', 'pkg', 'pkg-el']; exports.desc = 'Generate the pkg file'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/pkg-file'); }; ================================================ FILE: cmds/generate/recipe.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['recipe [destination]']; exports.desc = 'Generate the recipe file'; exports.builder = yargs => yargs .positional( 'destination', { description: 'destination path/folder', alias: 'dest', type: 'string', }) .options({ 'yes': { description: 'assume the answer to all prompts is yes', alias: 'y', type: 'boolean', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/recipe' , argv.dest , UTIL.def_flag(argv.yes, '--yes')); }; ================================================ FILE: cmds/generate/test/buttercup.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['buttercup']; exports.desc = 'Create a new Buttercup setup for the project'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/test/buttercup', argv.file); }; ================================================ FILE: cmds/generate/test/ecukes.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ecukes']; exports.desc = 'Create a new Ecukes setup for the project'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/test/ecukes', argv.file); }; ================================================ FILE: cmds/generate/test/ert-runner.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ert-runner [names..]']; exports.desc = 'Create a new test project for the ert-runner'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify test names', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/test/ert-runner', argv.names); }; ================================================ FILE: cmds/generate/test/ert.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ert [names..]']; exports.desc = 'Create a new test project for the ert tests'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify test names', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/test/ert', argv.names); }; ================================================ FILE: cmds/generate/test.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['test ']; exports.desc = 'Generate test files based on the testing framework'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask generate test [options..]`) .commandDir('./test/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 2) { yargs.positional( '', { description: 'type of the testing framework', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/generate/workflow/circle-ci.js ================================================ /** * Copyright (C) 2023 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['circle-ci [file]']; exports.desc = 'Generate the CircleCI workflow yaml file'; exports.builder = yargs => yargs .positional( '[file]', { description: 'name of the test file; the default is `config.yml`', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/workflow/circle-ci', argv.file); }; ================================================ FILE: cmds/generate/workflow/github.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['github [file]', 'github-actions [file]', 'gha [file]']; exports.desc = 'Generate the GitHub Actions workflow yaml file'; exports.builder = yargs => yargs .positional( '[file]', { description: 'name of the test file; the default is `test.yml`', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/workflow/github', argv.file); }; ================================================ FILE: cmds/generate/workflow/gitlab.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['gitlab [file]', 'gitlab-runner [file]']; exports.desc = 'Generate the GitLab Runner workflow yaml file'; exports.builder = yargs => yargs .positional( '[file]', { description: 'name of the test file; the default is `.gitlab-ci.yml`', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/workflow/gitlab', argv.file); }; ================================================ FILE: cmds/generate/workflow/travis-ci.js ================================================ /** * Copyright (C) 2023 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['travis-ci [file]']; exports.desc = 'Generate the Travis CI workflow yaml file'; exports.builder = yargs => yargs .positional( '[file]', { description: 'name of the test file; the default is `.travis.yml`', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'generate/workflow/travis-ci', argv.file); }; ================================================ FILE: cmds/generate/workflow.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['workflow ']; exports.desc = 'Generate yaml files for CI/CD'; exports.builder = function (yargs) { yargs.usage(`${exports.desc} Usage: eask generate workflow [options..]`) .commandDir('./workflow/') .demandCommand(); /* XXX: Configure only in the menu. */ if (UTIL.cmd_count() == 2) { yargs.positional( '', { description: 'type of the workflow', }); } } exports.handler = async (argv) => { }; ================================================ FILE: cmds/link/add.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['add ']; exports.desc = 'Link a local package'; exports.builder = yargs => yargs .positional( '', { description: 'name of the link', type: 'string', }) .positional( '', { description: 'location (target package) where you want to link', type: 'string', }) .options({ 'output': { description: 'output result to a file; the default is `LICENSE`', alias: 'o', type: 'string', group: TITLE_CMD_OPTION, }, }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'link/add', argv.name, argv.path); }; ================================================ FILE: cmds/link/delete.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['delete [names..]', 'remove [names..]']; exports.desc = 'Delete locally linked packages'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'name of the link, accept array', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'link/delete', argv.names); }; ================================================ FILE: cmds/link/list.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['list']; exports.desc = 'List all project links'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'link/list'); }; ================================================ FILE: cmds/lint/checkdoc.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['checkdoc [files..]']; exports.desc = 'Run checkdoc'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want checkdoc to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/checkdoc', argv.files); }; ================================================ FILE: cmds/lint/declare.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['declare [files..]']; exports.desc = 'Run check-declare'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want check-declare to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/declare', argv.files); }; ================================================ FILE: cmds/lint/elint.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['elint [files..]']; exports.desc = 'Run elint'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want elint to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/elint', argv.files); }; ================================================ FILE: cmds/lint/elisp-lint.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['elisp-lint [files..]']; exports.desc = 'Run elisp-lint'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want elisp-lint to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/elisp-lint', argv.files); }; ================================================ FILE: cmds/lint/elsa.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['elsa [files..]']; exports.desc = 'Run elsa'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want elsa to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/elsa', argv.files); }; ================================================ FILE: cmds/lint/indent.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['indent [files..]']; exports.desc = 'Run indent-lint'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want indent-lint to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/indent', argv.files); }; ================================================ FILE: cmds/lint/keywords.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['keywords']; exports.desc = `Run keywords checker (built-in)`; exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/keywords'); }; ================================================ FILE: cmds/lint/license.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['license']; exports.desc = `Run license check`; exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/license'); }; ================================================ FILE: cmds/lint/org.js ================================================ /** * Copyright (C) 2025-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['org [files..]']; exports.desc = `Run org-lint on Org files`; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want org-lint to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/org', argv.files); }; ================================================ FILE: cmds/lint/package.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['package [files..]']; exports.desc = 'Run package-lint'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify files to do package lint', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/package', argv.files); }; ================================================ FILE: cmds/lint/regexps.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['regexps [files..]', 'relint [files..]']; exports.desc = 'Run relint'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files you want relint to run on', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'lint/regexps', argv.files); }; ================================================ FILE: cmds/run/command.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['command [names..]', 'cmd [names..]']; exports.desc = 'Run elisp commands named [names..]'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'list of function commands to execute', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'run/command' , argv.names); }; ================================================ FILE: cmds/run/script.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const fs = require('fs'); const child_process = require("child_process"); exports.command = ['script [names..]']; exports.desc = 'Run script named [names..]'; exports.builder = yargs => yargs .positional( '[names..]', { description: 'specify scripts to run', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'run/script', argv.names); if (!fs.existsSync(EASK_HOMEDIR)) { return; } let run = EASK_HOMEDIR + 'run'; if (!fs.existsSync(run)) { return; } // this contain the full command! let instruction = fs.readFileSync(run, 'utf8'); let commands = instruction.split('\n').filter(element => element); let count = 0; startCommand(commands, count); }; /** * Recursive to execute commands in order. * * @param { array } commands - An array of commands to execute. * @param { integer } count - The current executing command's index. */ function startCommand(commands, count) { if (commands.length <= count) return; let command = commands[count]; console.warn('[RUN]: ' + command); let proc = spawn(command, { stdio: 'inherit', shell: true }); proc.on('close', function (code) { if (code == 0) { startCommand(commands, ++count); // start next command! return; } process.exit(code); }); } /** * Spawn process to avoid `MODULE_NOT_FOUND` not found error, * see https://github.com/vercel/pkg/issues/1356. * * @param { String } command - Command string. * @param { JSON } options - Process options. * @return Process object. */ function spawn(command, options) { if (IS_PKG && command.includes('eask ')) { let cmds = command.split(' '); cmds = replaceEaskExec(cmds); return child_process.spawn(process.execPath, cmds, options); } return child_process.spawn(command, options); } /** * Replace all possible eask/cli executable to snapshot executable. * @param { Array } cmds - Command array. * @return Return updated command array. */ function replaceEaskExec(cmds) { for (let index = 0; index < cmds.length; ++index) { if (cmds[index] == "eask") { cmds[index] = process.argv[1]; // XXX: This is `/snapshot/cli/eask` } } return cmds; } ================================================ FILE: cmds/source/add.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['add [url]']; exports.desc = 'Add an archive source'; exports.builder = yargs => yargs .positional( '', { description: 'name of the archive', type: 'array', }) .positional( '[url]', { description: 'link to the archive', type: 'string', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'source/add', argv.name, argv.url); }; ================================================ FILE: cmds/source/delete.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['delete ', 'remove ']; exports.desc = 'Remove an archive source'; exports.builder = yargs => yargs .positional( '', { description: 'name of the archive', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'source/delete', argv.name); }; ================================================ FILE: cmds/source/list.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['list']; exports.desc = 'List all source information'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'source/list'); }; ================================================ FILE: cmds/test/activate.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['activate [files..]']; exports.desc = 'Activate package; use to test package activation'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'files to load after package activation', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'test/activate', argv.files); }; ================================================ FILE: cmds/test/buttercup.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['buttercup [directories..]']; exports.desc = 'Run buttercup tests'; exports.builder = yargs => yargs .positional( '[directories..]', { description: 'directories containing buttercup tests, must be children of the current directory', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'test/buttercup', argv.directories); }; ================================================ FILE: cmds/test/ecukes.js ================================================ /** * Copyright (C) 2024-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ecukes [files..]']; exports.desc = 'Run ecukes tests'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify feature files to do ecukes tests', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'test/ecukes', argv.files); }; ================================================ FILE: cmds/test/ert-runner.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ert-runner [files..]']; exports.desc = 'Run ert tests using ert-runner'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify files to do ert tests', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'test/ert-runner', argv.files); }; ================================================ FILE: cmds/test/ert.js ================================================ /** * Copyright (C) 2022-2023 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['ert [files..]']; exports.desc = 'Run ert tests'; exports.builder = yargs => yargs .positional( '[files..]', { description: 'specify files to do ert tests', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'test/ert', argv.files); }; ================================================ FILE: cmds/test/melpazoid.js ================================================ /** * Copyright (C) 2023-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['melpazoid [directories..]']; exports.desc = 'Run melpazoid tests'; exports.builder = yargs => yargs .positional( '[directories..]', { description: 'specify directories to do melpazoid tests', type: 'array', }); exports.handler = async (argv) => { await UTIL.e_call(argv, 'test/melpazoid', argv.directories); }; ================================================ FILE: cmds/util/locate.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['locate']; exports.desc = 'Show the location where Eask is installed'; exports.handler = async (argv) => { console.log(UTIL.plugin_dir()); }; ================================================ FILE: cmds/util/root.js ================================================ /** * Copyright (C) 2025-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; exports.command = ['root']; exports.desc = UTIL.hide_cmd('Display the effective installation directory of your Emacs packages'); exports.handler = async (argv) => { await UTIL.e_call(argv, 'util/root'); }; ================================================ FILE: cmds/util/upgrade-eask.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const child_process = require("child_process"); exports.command = ['upgrade-eask', 'upgrade-self']; exports.desc = 'Upgrade Eask itself'; exports.handler = async (argv) => { process.chdir(UTIL.plugin_dir()); let proc = child_process.spawn('git', ['pull'], { stdio: 'inherit' }); // You would just need to register the error event, or else it can't print // the help instruction below. proc.on('error', function () { }); proc.on('close', function (code) { if (code == 0) { process.stdout.write('✓ Done upgrading Eask to the latest version'); return; } // Help instruction here! console.log(''); console.log(''); console.log('✗ Failed to upgrade Eask, possible causes are:'); console.log(''); console.log(' [1] Make sure you have git installed and has the right permission'); console.log(' [2] You install Eask with other package management tool'); console.log(''); console.log('For example, if you have installed eask with npm:'); console.log(''); console.log(' $ npm install -g @emacs-eask/cli@latest'); console.log(''); process.stdout.write('Visit https://emacs-eask.github.io/Getting-Started/Install-Eask/ to see all available install options'); }); }; ================================================ FILE: docs/.gitignore ================================================ vendor _site .bundle /archetypes .hugo_build.lock /public # macOS .DS_Store ================================================ FILE: docs/config/_default/config.yaml ================================================ --- baseURL: https://emacs-eask.github.io/ title: Emacs Eask theme: geekdoc pygmentsUseClasses: true pygmentsCodeFences: true timeout: 180000 pluralizeListTitles: false defaultContentLanguage: en disablePathToLower: true enableGitInfo: false enableRobotsTXT: true params: geekdocSubtitle: Learn once, run anywhere geekdocToC: 3 geekdocLogo: './logo.png' geekdocRepo: 'https://github.com/emacs-eask/cli/' geekdocEditPath: 'edit/master/docs/' geekdocSearch: true geekdocSearchShowParent: true geekdocLegalNotice: "/tos" geekdocPrivacyPolicy: "/tos" geekdocContentLicense: name: CC BY-SA 4.0 link: https://creativecommons.org/licenses/by-sa/4.0/ markup: goldmark: renderer: unsafe: true tableOfContents: startLevel: 1 endLevel: 9 taxonomies: tag: tags outputs: home: - HTML page: - HTML section: - HTML taxonomy: - HTML term: - HTML security: exec: allow: - "^asciidoctor$" ================================================ FILE: docs/config/_default/languages.yaml ================================================ --- en: languageName: "English" weight: 100 zh-TW: languageName: "繁體中文" weight: 90 ================================================ FILE: docs/content/Continuous-Integration/CircleCI/_index.en.md ================================================ --- title: 💠 CircleCI weight: 400 --- {{< toc >}} [![Windows](https://img.shields.io/badge/-Windows-lightblue?logo=windows&style=flat&logoColor=blue)](#) [![macOS](https://img.shields.io/badge/-macOS-lightgrey?logo=apple&style=flat&logoColor=white)](#) [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) Example to use [Circle CI](https://circleci.com/). ```yml version: 2.1 orbs: win: circleci/windows@2.2.0 # Default actions to perform on each Emacs version commands: setup-linux: steps: - checkout - run: name: Install unzip command: apt-get update && apt-get install unzip - run: name: Install Eask command: curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh setup-macos: steps: - checkout - run: name: Install Emacs latest command: | echo "HOMEBREW_NO_AUTO_UPDATE=1" >> $BASH_ENV brew install homebrew/cask/emacs - run: name: Install unzip command: apt-get update && apt-get install unzip - run: name: Install Eask command: curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh setup-windows: steps: - checkout - run: name: Install Eask command: url.exe -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.bat | cmd /Q test: steps: - run: name: Run regression tests command: eldev -dtT -p test lint: steps: - run: name: Run Elisp-lint command: eldev lint - run: name: Byte-compile `.el' files command: eldev -dtT compile --warnings-as-errors jobs: test-ubuntu-emacs-26: docker: - image: silex/emacs:26-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-27: docker: - image: silex/emacs:27-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-28: docker: - image: silex/emacs:28-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-29: docker: - image: silex/emacs:29-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-30: docker: - image: silex/emacs:30-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-master: docker: - image: silex/emacs:master-ci entrypoint: bash steps: - setup-linux - test test-macos-emacs-latest: macos: xcode: "14.0.0" steps: - setup-macos - test test-windows-emacs-latest: executor: win/default steps: - run: name: Install Emacs latest command: | choco install emacs - setup-windows - test workflows: version: 2 ci-test-matrix: jobs: - test-ubuntu-emacs-26 - test-ubuntu-emacs-27 - test-ubuntu-emacs-28 - test-ubuntu-emacs-29 - test-ubuntu-emacs-30 - test-ubuntu-emacs-master - test-macos-emacs-latest - test-windows-emacs-latest ``` This example is testing your Emacs Lisp package in the below environment; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Ubuntu) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | `snapshot` | latest | | Windows | `snapshot` | latest | {{< hint info >}} 💡 You can generate workflow file via `eask generate workflow circle-ci`, see [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-circle-ci) for more information! {{< /hint >}} ================================================ FILE: docs/content/Continuous-Integration/CircleCI/_index.zh-tw.md ================================================ --- title: 💠 CircleCI weight: 400 --- {{< toc >}} [![Windows](https://img.shields.io/badge/-Windows-lightblue?logo=windows&style=flat&logoColor=blue)](#) [![macOS](https://img.shields.io/badge/-macOS-lightgrey?logo=apple&style=flat&logoColor=white)](#) [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) 使用 [Circle CI](https://circleci.com/) 的示例。 ```yml version: 2.1 orbs: win: circleci/windows@2.2.0 # 對每個 Emacs 版本執行的默認操作 commands: setup-linux: steps: - checkout - run: name: Install unzip command: apt-get update && apt-get install unzip - run: name: Install Eask command: curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh setup-macos: steps: - checkout - run: name: Install Emacs latest command: | echo "HOMEBREW_NO_AUTO_UPDATE=1" >> $BASH_ENV brew install homebrew/cask/emacs - run: name: Install unzip command: apt-get update && apt-get install unzip - run: name: Install Eask command: curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh setup-windows: steps: - checkout - run: name: Install Eask command: url.exe -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.bat | cmd /Q test: steps: - run: name: Run regression tests command: eldev -dtT -p test lint: steps: - run: name: Run Elisp-lint command: eldev lint - run: name: Byte-compile `.el' files command: eldev -dtT compile --warnings-as-errors jobs: test-ubuntu-emacs-26: docker: - image: silex/emacs:26-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-27: docker: - image: silex/emacs:27-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-28: docker: - image: silex/emacs:28-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-29: docker: - image: silex/emacs:29-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-30: docker: - image: silex/emacs:30-ci entrypoint: bash steps: - setup-linux - test test-ubuntu-emacs-master: docker: - image: silex/emacs:master-ci entrypoint: bash steps: - setup-linux - test test-macos-emacs-latest: macos: xcode: "14.0.0" steps: - setup-macos - test test-windows-emacs-latest: executor: win/default steps: - run: name: Install Emacs latest command: | choco install emacs - setup-windows - test workflows: version: 2 ci-test-matrix: jobs: - test-ubuntu-emacs-26 - test-ubuntu-emacs-27 - test-ubuntu-emacs-28 - test-ubuntu-emacs-29 - test-ubuntu-emacs-30 - test-ubuntu-emacs-master - test-macos-emacs-latest - test-windows-emacs-latest ``` 此示例在以下環境中測試您的 Emacs Lisp 包; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Ubuntu) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | `snapshot` | latest | | Windows | `snapshot` | latest | {{< hint info >}} 💡 您可以通過 `eask generate workflow circle-ci` 生成工作流文件, 參見[命令和選項](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-circle-ci) 獲取更多信息! {{< /hint >}} ================================================ FILE: docs/content/Continuous-Integration/GitHub-Actions/_index.en.md ================================================ --- title: 💿 GitHub Actions weight: 100 --- {{< toc >}} [![Windows](https://img.shields.io/badge/-Windows-lightblue?logo=windows&style=flat&logoColor=blue)](#) [![macOS](https://img.shields.io/badge/-macOS-lightgrey?logo=apple&style=flat&logoColor=white)](#) [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) Here is an example using the [GitHub](https://github.com/) Actions service. ```yaml jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: [26.3, 27.2, 28.2, 29.4, 30.2, snapshot] steps: - uses: actions/checkout@v5 # Install Emacs - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} # Install Eask - uses: emacs-eask/setup-eask@master with: version: 'snapshot' - name: Run tests run: | eask package eask install eask compile ``` This example is testing your Emacs Lisp package in the below environment; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Ubuntu) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | Windows | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | with these following `actions`, - [setup-emacs][] to install Emacs - [setup-eask][] to install desired Eask version {{< hint info >}} 💡 You can generate workflow file via `eask generate workflow github`, see [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-github) for more information! {{< /hint >}} ## 💾 Setup Eask locally You can install Eask locally using scripts from `.github/scripts/setup-eask` (Unix) or `.github/scripts/setup-eask.ps1` (Windows). ```yaml - uses: actions/checkout@v5 - name: Prepare Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: Prepare Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 ``` [setup-emacs]: https://github.com/jcs090218/setup-emacs [setup-eask]: https://github.com/emacs-eask/setup-eask ================================================ FILE: docs/content/Continuous-Integration/GitHub-Actions/_index.zh-tw.md ================================================ --- title: 💿 GitHub Actions weight: 100 --- {{< toc >}} [![Windows](https://img.shields.io/badge/-Windows-lightblue?logo=windows&style=flat&logoColor=blue)](#) [![macOS](https://img.shields.io/badge/-macOS-lightgrey?logo=apple&style=flat&logoColor=white)](#) [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) 以下是使用 [GitHub](https://github.com/) Actions 服務的示例。 ```yaml jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] emacs-version: [26.3, 27.2, 28.2, 29.4, 30.2, snapshot] steps: - uses: actions/checkout@v5 # 安裝 Emacs - uses: jcs090218/setup-emacs@master with: version: ${{ matrix.emacs-version }} # 安裝 Eask - uses: emacs-eask/setup-eask@master with: version: 'snapshot' - name: Run tests run: | eask package eask install eask compile ``` 此示例在以下環境中測試您的 Emacs Lisp 包; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Ubuntu) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | Windows | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | 通過以下`操作`, - [setup-emacs][] 安裝 Emacs - [setup-eask][] 安裝所需的 Eask 版本 {{< hint info >}} 💡 您可以通過 `eask generate workflow github` 生成工作流文件, 參見[命令和選項](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-github) 了解更多信息! {{< /hint >}} ## 💾 在本地設置 Eask 您可以使用 `.github/scripts/setup-eask` (Unix) 或 `.github/scripts/setup-eask.ps1` (Windows) 中的腳本在本地安裝 Eask。 ```yaml - uses: actions/checkout@v5 - name: 準備 Eask (Unix) if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' run: | chmod -R 777 ./ .github/scripts/setup-eask - name: 準備 Eask (Windows) if: matrix.os == 'windows-latest' run: .github/scripts/setup-eask.ps1 ``` [setup-emacs]: https://github.com/jcs090218/setup-emacs [setup-eask]: https://github.com/emacs-eask/setup-eask ================================================ FILE: docs/content/Continuous-Integration/GitLab-Runner/_index.en.md ================================================ --- title: 🦊 GitLab Runner weight: 200 --- {{< toc >}} [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) Example to use [GitLab](https://gitlab.com/) runner. ```yml default: before_script: - apt-get update - apt-get install unzip - curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" test-26.3: image: silex/emacs:26.3-ci script: - eask clean all - eask package - eask install - eask compile test-27.2: image: silex/emacs:27.2-ci script: - eask clean all - eask package - eask install - eask compile test-28.2: image: silex/emacs:28.2-ci script: - eask clean all - eask package - eask install - eask compile test-29.4: image: silex/emacs:29.4-ci script: - eask clean all - eask package - eask install - eask compile test-30.2: image: silex/emacs:30.2-ci script: - eask clean all - eask package - eask install - eask compile ``` This example is testing your Emacs Lisp package in the below environment; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Debian) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | n/a | latest | | Windows | n/a | latest | {{< hint info >}} 💡 You can generate workflow file via `eask generate workflow gitlab`, see [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-gitlab) for more information! {{< /hint >}} ================================================ FILE: docs/content/Continuous-Integration/GitLab-Runner/_index.zh-tw.md ================================================ --- title: 🦊 GitLab Runner weight: 200 --- {{< toc >}} [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) 使用 [GitLab](https://gitlab.com/) 運行程序的示例。 ```yml default: before_script: - apt-get update - apt-get install unzip - curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" test-26.3: image: silex/emacs:26.3-ci script: - eask clean all - eask package - eask install - eask compile test-27.2: image: silex/emacs:27.2-ci script: - eask clean all - eask package - eask install - eask compile test-28.2: image: silex/emacs:28.2-ci script: - eask clean all - eask package - eask install - eask compile test-29.4: image: silex/emacs:29.4-ci script: - eask clean all - eask package - eask install - eask compile test-30.2: image: silex/emacs:30.2-ci script: - eask clean all - eask package - eask install - eask compile ``` 此示例在以下環境中測試您的 Emacs Lisp 包; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Debian) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | n/a | latest | | Windows | n/a | latest | {{< hint info >}} 💡 您可以通過 `eask generate workflow gitlab` 生成工作流文件, 參見[命令和選項](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-gitlab) 了解更多信息! {{< /hint >}} ================================================ FILE: docs/content/Continuous-Integration/Travis-CI/_index.en.md ================================================ --- title: 📀 Travis CI weight: 300 --- {{< toc >}} [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) Example to use [Travis CI](https://www.travis-ci.com/). ```yml language: nix os: - linux - osx env: - EMACS_CI=emacs-26-3 - EMACS_CI=emacs-27-2 - EMACS_CI=emacs-28-2 - EMACS_CI=emacs-29-4 - EMACS_CI=emacs-30-1 - EMACS_CI=emacs-snapshot matrix: fast_finish: true allow_failures: - env: EMACS_CI=emacs-snapshot install: - bash <(curl https://raw.githubusercontent.com/purcell/nix-emacs-ci/master/travis-install) - curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" script: - eask package - eask install - eask compile ``` This example is testing your Emacs Lisp package in the below environment; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Ubuntu) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | n/a | latest | | Windows | n/a | latest | {{< hint info >}} 💡 You can generate workflow file via `eask generate workflow travis-ci`, see [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-travis-ci) for more information! {{< /hint >}} ================================================ FILE: docs/content/Continuous-Integration/Travis-CI/_index.zh-tw.md ================================================ --- title: 📀 Travis CI weight: 300 --- {{< toc >}} [![Linux](https://img.shields.io/badge/-Linux-fcc624?logo=linux&style=flat&logoColor=black)](#) 使用 [Travis CI](https://www.travis-ci.com/) 的示例。 ```yml language: nix os: - linux - osx env: - EMACS_CI=emacs-26-3 - EMACS_CI=emacs-27-2 - EMACS_CI=emacs-28-2 - EMACS_CI=emacs-29-4 - EMACS_CI=emacs-30-1 - EMACS_CI=emacs-snapshot matrix: fast_finish: true allow_failures: - env: EMACS_CI=emacs-snapshot install: - bash <(curl https://raw.githubusercontent.com/purcell/nix-emacs-ci/master/travis-install) - curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" script: - eask package - eask install - eask compile ``` 此示例在以下環境中測試您的 Emacs Lisp 包; | OS | Emacs | Eask | |----------------|----------------------------------------------------|--------| | Linux (Ubuntu) | `26.x`, `27.x`, `28.x`, `29.x`, `30.x`, `snapshot` | latest | | macOS | n/a | latest | | Windows | n/a | latest | {{< hint info >}} 💡 您可以通過`eask generate workflow travis-ci`生成工作流文件, 參見[命令和選項](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#-eask-generate-workflow-travis-ci) 了解更多信息! {{< /hint >}} ================================================ FILE: docs/content/Continuous-Integration/_index.en.md ================================================ --- title: Continuous Integration weight: 400 --- ================================================ FILE: docs/content/Continuous-Integration/_index.zh-tw.md ================================================ --- title: 持續整合 weight: 400 --- ================================================ FILE: docs/content/Contributing/Codebase-Overview/_index.en.md ================================================ --- title: 🔱 Codebase Overview weight: 10 --- Eask consists of two components: a command-line tool (the Eask CLI), and Elisp scripts. The CLI, is used to find the corresponding lisp file and feed it into the Emacs executable. It would parse all options and convert them to Emacs understandable options on the lisp scripts end. It is written in plain JavaScript, the main file is located in **src/util.js**. The Elisp scripts, is used to do the actual execution for each command that passes through the CLI. All commands are split into its file and are organized in the **lisp** folder. It is written in plain Emacs Lisp, the main file is located in **lisp/_prepare.el**. {{< toc >}} ## 🖥️ CLI & Yargs The yargs command file is written in JavaScript, and located under the **cmds** folder. Each file under, will be named with convention `[command_name].js`. This file should define basic command-line parsing rules and correctly prepare data to feed the Emacs session. Let's look at the file `cmds/core/archives.js`: ```js exports.command = ['archives', 'sources']; // alias to sources exports.desc = 'List out all package archives'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/archives'); }; ``` This is a standard yargs command file, which contains all the information we need to pass it to the Emacs session. - **exports.command** is the argument pattern, but it also accepts alias (array) - **exports.desc** is the command description - **exports.handler** is an asynchronous function that handles command execution - **UTIL** is a global variable that points to the `src/util.js` module. - **`'core/archives'`** is the elisp file under **lisp** folder (without `.el` extension). `eask` is a JavaScript file that holds all our global options. ```js yargs .usage('Usage: eask [options..]') .help( 'help', 'Show usage instructions.' ) .options({ 'global': { description: `change default workspace to ~/.eask/`, alias: 'g', type: 'boolean', }, }) ... ``` For **local** options, please use `exports.builder` and specify under its command file. See [yargs/docs/advanced.md](https://github.com/yargs/yargs/blob/main/docs/advanced.md), the official documentation for more information and getting a better explanation would help! ## 📜 Elisp Script Elisp scripts are located under the **lisp** folder and will wait to get called by the CLI. All Elisp scripts are written in Emacs Lisp and should have a similar structure below: ```elisp (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (message "PWD is %s" default-directory)) ``` See [Development API](https://emacs-eask.github.io/Development-API/) section for more information! ## 📂 Project Structure There are **three** places you need to look into it: 1. `eask` file at the root of the project 2. `cmds` folder with all available commands 3. `lisp` folder with all elisp code `eask` is the node entry, and the main yargs definition! `cmds` and `lisp` folders are command files that correspond to each other. ### ♻️ Execution Order inside Eask-file Eask is executed this way:

- **Eask environment** builds sandbox and reads Eask file information - **Emacs configuration** is only being executed when `-g` option is enabled - **before hooks** are hooks run before command task - **command execution** is the primary command task - **after hooks** are hooks run after command task ================================================ FILE: docs/content/Contributing/Codebase-Overview/_index.zh-tw.md ================================================ --- title: 🔱 代碼庫概述 weight: 10 --- Eask 由兩個組件組成:命令行工具(Eask CLI)和 Elisp 腳本。 CLI 用於查找相應的 lisp 文件並將其提供給 Emacs 可執行文件。 它會解析所有選項並在 lisp 腳本端將 它們轉換為 Emacs 可理解的選項。 它是用純 JavaScript 編寫的,主文件位於 **src/util.js** 中。 Elisp 腳本用於實際執行通過 CLI 的每個命令。 所有命令都拆分到它的文件中,並組織在 **lisp** 文件夾中。 它是用純 Emacs Lisp 編寫的,主要文件位於 **lisp/_prepare.el** 中。 {{< toc >}} ## 🖥️ CLI 和 Yargs yargs 命令文件是用 JavaScript 編寫的,位於 **cmds** 文件夾下。 下面的每個文件都將按照約定命名為 `[command_name].js`。 此文件應定義基本的命令行解析規則並正確準備數據以提供給 Emacs session。 讓我們看一下文件 `cmds/core/archives.js` : ```js exports.command = ['archives', 'sources']; // 來源的別名 exports.desc = 'List out all package archives'; exports.handler = async (argv) => { await UTIL.e_call(argv, 'core/archives'); }; ``` 這是一個標準的 yargs 命令文件,裡麵包含了我們所有的信息需要將它傳遞給 Emacs session。 - **exports.command** 是參數模式,但它也接受別名(數組) - **exports.desc** 是命令說明 - **exports.handler** 是處理命令執行的異步函數 - **UTIL** 是指向 `src/util.js` 模塊的全局變量。 - **`'core/archives'`** 是 **lisp** 文件夾下的 elisp 文件(沒有 .el 擴展名)。 `eask` 是一個包含我們所有全局選項的 JavaScript 文件。 ```js yargs .usage('Usage: eask [options..]') .help( 'help', 'Show usage instructions.' ) .options({ 'global': { description: `change default workspace to ~/.eask/`, alias: 'g', type: 'boolean', }, }) ... ``` 對於 **local** 選項,請使用 `exports.builder` 並在其下指定命令文件。 看 [yargs/docs/advanced.md](https://github.com/yargs/yargs/blob/main/docs/advanced.md), 官方文檔以獲取更多信息並獲得更好的解釋會有所幫助! ## 📜 Elisp 腳本 Elisp 腳本位於 **lisp** 文件夾下,將等待 CLI 調用。 所有 Elisp 腳本都是用 Emacs Lisp 編寫的, 並且應該具有以下類似的結構: ```elisp (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (message "PWD is %s" default-directory)) ``` 請參閱 [開發 API](https://emacs-eask.github.io/Development-API/) 部分了解更多信息! ## 📂 項目結構 您需要研究**三個**地方: 1. `eask` 項目根目錄下的文件 2. `cmds` 包含所有可用命令的文件夾 3. `lisp` 包含所有 elisp 代碼的文件夾 `eask` 是節點入口,主要的 yargs 定義! `cmds` 和 `lisp` 文件夾是一一對應的命令文件。 ### ♻️ Eask 文件中的執行順序 Eask 是這樣執行的:

- **Eask environment** 構建沙箱並讀取 Eask 文件信息 - **Emacs configuration** 僅在啟用 `-g` 選項時執行 - **before hooks** hook 在命令任務之前運行嗎 - **command execution** 是主要的命令任務 - **after hooks** 命令任務後是否運行 hook ================================================ FILE: docs/content/Contributing/Developing-Eask/_index.en.md ================================================ --- title: 🔨 Developing Eask weight: 20 --- {{< toc >}} ## 🚩 Prerequisites To make changes to Eask, you should have: 1. [Node.js][] for the development environment. 2. [npm][] for the package manager. 3. [Emacs][], 26.1 or above! ## 📝 Building To build the development environment, you would have to install Eask using the [build from source][Build from source] method. Make sure you have set up the environment PATH variable, so you can call `eask` from the terminal. After you have stepped through the installation, try: ```sh eask locate ``` It should print out the location of the `eask` executable. You should be able to identify the Eask executable's location, even you have multiple Eask versions installed! ## 🧪 Testing Local testing for Eask is done using the [Jest][] testing framework. Jest is a mature and well supported testing framework written in Javascript. Jest was chosen for much the [same reasons as Javascript][Why JS?] was chosen for this project. In addition, Jest is easy to learn and has built in support for snapshot based testing. ### ⚗️ Running Tests If you have not done so already, run `npm install --dev` Always run from the project root (i.e. same directory as `package.json`) - run all tests `npm run test` - run a single test `npm run test path/to/test.js` - run tests with full output `npm run test-debug` - remove files created during test `npm run test-reset` Since `npm run test` just runs Jest, you can also pass Jest options to the commands above. For example: - run tests whose names (in `test()` blocks) match `npm run test -t 'eask lint .*'` - re-run failed tests `npm run test -f` ### 🌍 Environment Vars | Name | Type | Default | Meaning | |:---------------|:--------|:--------|:--------------------------------------------------------------------------------------------------| | `ALLOW_UNSAFE` | `bool*` | false | Run tests in `testUnsafe` blocks. These can **overwrite** your personal emacs config or settings. | | `DEBUG` | `bool*` | false | Print full output from commands in test. | | `EASK_COMMAND` | path | "eask" | Path to Eask. Usually either `eask` or `$PWD/bin/eask` to use local changes. | | `TIMEOUT` | number | 25000 | Command timeout in ms. Note this is different than Jest's timeout, which should be greater. | {{< hint info >}} 💡 Node.js handles environment variables as strings. That means that `DEBUG=0`, `DEBUG=false` all _enable_ `DEBUG`. The only setting which disables a boolean flag is null, for example `DEBUG=`. {{< /hint >}} ### 🔬 How to Write a Test **Folder structure** Tests should be in `test/js`. Related tests should be in the same file with the suffix `.test.js` and are usually named after the feature or command that they test, for example `link.test.js` tests the `eask link` command. If the test needs some specific project files, put them in a new folder within `test/js` For example, files in `test/js/foo` would be expected to be for `foo.test.js`. The exception is `test/js/empty`, which is simply an empty folder. If you use it, make sure to run `eask clean all` before your tests. **Test File structure** ``` javascript const { TestContext } = require("./helpers"); describe("emacs", () => { const ctx = new TestContext("./test/jest/empty"); beforeAll(async () => await ctx.runEask("clean all")); afterAll(() => ctx.cleanUp); test("eask emacs --version", async () => { await ctx.runEask("emacs --version"); }); test("eask emacs --batch --eval", async () => { await ctx.runEask( 'emacs --batch --eval "(require (quote ert))" --eval "(ert-deftest mytest () (should-not (display-graphic-p)))" -f ert-run-tests-batch', ); }); }); ``` In Jest, you group related tests using `describe`. Tests in the same `describe` block can share setup/teardown code, can be disabled as a group and are grouped under the same heading in output. `describe` blocks can be nested within other `describe` blocks. It's a good idea to add a nested `describe` when tests run in different directories, or to match a "given, when, then" style of testing. For each test directory you should create a new `TestContext` object. All `runEask` commands will use the `TestContext`'s working directory. Jest's tests are in `test` blocks. Note that `it` is an alias for `test`. Tests can be selectively disabled in code, like so: - `test.only(name, fn)` runs only that test in the file - `test.skip(name, fn)` skips running the test but still prints its name - `test.failing(name, fn)` invert the meaning of the test: it *should* fail. The `expect` API matches values in different ways and usually prints a diff as part of the failure report. See Jest's [expect()](https://jestjs.io/docs/expect) API for more info. Uncaught errors thrown in a `test` block will fail it and report the error. That's why many tests don't have an `expect` call, they simply check that the command succeeds. Output from `runEask` is wrapped in a helper class `CommandOutput` which provides some transformation methods. For example, if you have `const out = await ctx.runEask("analyze");`, then - `out.combined()` concatenates both stdout and stderr as a string, - `out.raw()` returns a plain object with just `stdout` and `stderr` as properties, - `out.sanitized()` replaces all absolute paths that match the context's path Since the class wraps the output of Node's `exec()` method you can still access `stdout` and `stderr`: ``` javascript const { stderr, stdout } = await ctx.runEask("analyze"); ``` Some commands create files or directories which should be removed after the test runs. For example, `eask generate ignore elisp` creates a `.gitignore` file. You can use the context's `removeFiles` method to remove files and directories relative to the context's path: ``` javascript describe("Generating", () => { beforeAll(async () => await ctx.removeFiles(".gitignore")); afterAll(async () => await ctx.removeFiles(".gitignore")); it("eask generate ignore elisp", async () => { await ctx.runEask("generate ignore elisp"); }); }); ``` Note that `removeFiles()` will recursively remove directories, but does not accept patterns. So, to remove all files in `./test` just call `ctx.remove("test")`. You can pass multiple files or directory names in single call: `ctx.remove("test", ".gitignore")`. Use `TestContext.cleanUp()` to immediately abort any still-running commands that were called in that context. Use this if Jest reports "open handles were detected" after a test run. Note that `cleanUp` sends a signal to *all* processes started using the context's `runEask` command. If used in an `afterEach` hook (i.e. after every test) it may result in failures. ### 🪧 Snapshots [Snapshot tests](https://jestjs.io/docs/snapshot-testing) match the output of a test against a saved copy of the expected output. For example: ``` javascript test("eask analyze", async () => { const res = await ctx.runEask("analyze"); expect(res.raw()).toMatchSnapshot(); }); ``` The first time you run this Jest will create a new snapshot saved in an adjacent `__snapshot__` directory. You should check this file in to version control as it forms a critical part of the test. If the snapshot changes, you can update the snapshot by running Jest with option `-u`, for example, `npm run test -- -u` will update all changed snapshots. Any type of output can be used for a snapshot test. You could snapshot the contents of a file after changing it ``` javascript test("eask analyze", async () => { await ctx.runEask("foo"); const file = ctx.fileContents("Easkfile"); // file as a string expect(file).toMatchSnapshot(); }); ``` Often snapshots will include data that varies with time or environment, for example timestamps or file paths. The snapshot of `eask analyze` contains absolute file paths that will be different on every machine. Output from `runEask` is wrapped in a helper class `CommandOutput` which provides some transformation methods. The simplest just removes the absolute file paths: ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res.sanitized() // a CommandOutput object with absolute paths replaced by "~" .raw(); // an object { stderr, stdout } suitable for snapshotting expect(resClean).toMatchSnapshot(); }); ``` You can include custom replacement functions. Here, numbers will be replaced by `"x"`. Then strings `"x:x"` will be replaced by `"y"`. ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res .sanitized( (x) => x.replace(/[0-9]+/g, "x"), (x) => x.replaceAll("x:x", "y"), ) .raw(); expect(resClean).toMatchSnapshot(); }); ``` It's important to use the `g` regex flag so all occurrences of the match are replaced, or you could use `replaceAll`. User provided functions run in addition to the default sanitize function and run in the order they were given. ### ⏱️ Timeouts There are two timeout settings, one for Jest and one for Node's `exec()`. All timeout values are in milliseconds. Since the `exec()` timeout immediately terminates the running command and reports output, it is much better to use that instead of Jest's timeout. To change a timeout for a single command ``` javascript ctx.runEask("analyze", { timeout: 10000}) ``` To change the global timeout for a single run, use the env var ``` shell env TIMEOUT=30000 npm run test ``` To change the global timeout permanently, set the default in `./helpers.js`. If you change either global timeout, **make sure the global Jest timeout is greater** by setting it in `package.json` ``` json "jest": { "rootDir": "./test/jest", "testTimeout": 40000 } ``` ### 📜 Patterns Here are some common patterns for testing commands. Each of these assumes that `ctx` is a `TestContext` object. **Check a command succeeds:** ``` javascript test("eask analyze", async () => { await ctx.runEask("analyze"); }); ``` Uncaught errors thrown in a `test` block will fail the test and report the error. Failed commands will include stderr and stdout. **Check a command fails:** ``` javascript test("eask analyze", async () => { await expect(ctx.runEask("analyze")).rejects.toThrow(); }); ``` **Check a command fails with a specific code:** ``` javascript test("eask link add should error", async () => { // the error object should have property code = 1 await expect(ctx.runEask("link add")).rejects.toMatchObject({ code: 1, }); }); ``` **Check a command produces some output:** ``` javascript test("eask analyze", async () => { const out = await ctx.runEask("analyze"); expect(out.stderr).toMatch("success"); // should apppear as a substring // If you want to check both `stderr` and `stdout`, just concatenate them expect(out.stdout + "/n" + out.stderr).toMatch("success"); // Same thing using helper methods expect(out.combined()).toMatch("success"); }); ``` **Check command output against a snapshot:** Simple output matching ``` javascript test("eask analyze", async () => { const res = await ctx.runEask("analyze"); expect(res).toMatchSnapshot(); }); ``` Update all changed snapshots: `npm run test -- -u` Remove absolute file paths from output: ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res.sanitized() // a CommandOutput object with absolute paths replaced by "~" .raw(); // an object { stderr, stdout } suitable for snapshotting expect(resClean).toMatchSnapshot(); }); ``` Apply custom transformations for sanitizing output: ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res .sanitized( (x) => x.replace(/[0-9]+/g, "x"), (x) => x.replace(/x:x/g, "y"), ) .raw(); expect(resClean).toMatchSnapshot(); }); ``` User provided functions run in addition to the default sanitize function and run in the order they were given. **Commands which modify the user's environment:** For example, commands which use `-c` or `-g` options. ``` javascript const { testUnsafe } = require('./helpers'); // this will only run if ALLOW_UNSAFE is != 0 testUnsafe("global install", async () => { // this installs in ~/.eask and changes ~/Eask await ctx.runEask("install -g foo"); }); ``` ### 🩺 Common Problems - When using `runEask()`, pass only the Eask *arguments*, not the `eask` command itself. - Always `await` any expressions that trigger commands. - When using `expect(...).rejects` it should be awaited so that the promise rejects before the test completes. - The folder argument to `TestContext` should be relative to project root, if it doesn't exist you may get an error `ENOENT` - If you get an error from Jest reporting open handles, then try using `afterAll(() => ctx.cleanUp())` - There are two timeout values: one used for Jest (set in `package.json`), and one used for `node.exec`, set via env var in `./helpers.js`. The `node.exec` timeout is set lower than the Jest one, so changing timeout values for tests or by `jest.setTimeout` usually won't have an effect. Instead set the timeout on the command itself `runEask("eask emacs", { timeout: 100000 })` [Build from source]: https://emacs-eask.github.io/Getting-Started/Install-Eask/#-build-from-source [Why JS?]: https://emacs-eask.github.io/FAQ/#-why-javascript [Node.js]: https://nodejs.org/en/ [npm]: https://www.npmjs.com/ [yargs]: https://github.com/yargs/yargs [Emacs]: https://www.gnu.org/software/emacs/ [Jest]: https://jestjs.io ================================================ FILE: docs/content/Contributing/Developing-Eask/_index.zh-tw.md ================================================ --- title: 🔨 開發 Eask weight: 20 --- {{< toc >}} ## 🚩 必備條件 要更改 Eask,您應該: 1. [Node.js][] 開發環境。 2. [npm][] 包管理器。 3. [Emacs][], 26.1 以上! ## 📝 建構 要構建開發環境,您必須使用安裝 Eask [從源代碼建構][Build from source] 的方法。 確保你已經設置了環境 `PATH` 變量,這樣你就可以調用來自終端的`eask`。 完成安裝後,嘗試: ```sh eask locate ``` 它應該會列印出 `eask` 可執行檔的位置。即使您已安裝多個 Eask 版本,也應該可以辨識 Eask 可執行檔的位置! ## 🧪 測試 Eask 的本地測試使用 [Jest][] 測試框架完成。Jest 是用 Javascript 寫成的成熟且支援良好的測試框架。 選擇 Jest 的原因與本專案[選擇 Javascript 的原因大致相同][Why JS?]。此外,Jest 容易學習,而且內建了對快照測試的支援。 ### ⚗️ 執行測試 如果尚未執行,請執行 `npm install --dev` 。 永遠從專案根目錄執行 (即與 `package.json` 相同的目錄) - 執行所有測試 `npm run test` - 執行單次測試 `npm run test path/to/test.js` - 以完整輸出執行測試 `npm run test-debug` - 移除測試時建立的檔案 `npm run test-reset` 由於 `npm run test` 只會執行 Jest,您也可以將 Jest 選項傳給上面的指令。例如 - 執行名稱 (在 `test()` 區塊中) 符合`npm run test -t 'eask lint .*'` 的測試 - 重新執行失敗的測試 `npm run test -f` ### 🌍 環境變數 | Name | Type | Default | Meaning | |:---------------|:--------|:--------|:---------------------------------------------------------------------| | `ALLOW_UNSAFE` | `bool*` | false | 在 `testUnsafe` 區塊中執行測試。這些區塊可能**覆寫**您個人的 emacs 配置或設定。 | | `DEBUG` | `bool*` | false | 列印測試中指令的完整輸出。 | | `EASK_COMMAND` | path | "eask" | Eask 的路徑。通常是 `eask` 或 `$PWD/bin/eask` 來使用本機變更。 | | `TIMEOUT` | number | 25000 | 命令超時,以毫秒為單位。請注意這與 Jest 的逾時時間不同,Jest 的逾時時間應該更長。 | {{< hint info >}} 💡 Node.js 將環境變數當成字串來處理。這表示 `DEBUG=0`、`DEBUG=false` 都 _enable_ `DEBUG`。 唯一禁用布林旗標的設定是 null,例如 `DEBUG=`。 {{< /hint >}} ### 🔬 如何撰寫測試 **資料夾結構** 測試應該放在 `test/js` 中。 相關的測試應該在同一個檔案中,後綴為 `.test.js`,通常以測試的功能或指令命名,例如 `link.test.js` 測試 `eask link` 指令。 如果測試需要一些特定的專案檔案,請將它們放在 `test/js` 中的新資料夾。 例如,在 `test/js/foo` 中的檔案預計會用於 `foo.test.js`。 例外的是 `test/js/empty`,它只是一個空的資料夾。 如果您使用它,請務必在測試前執行 `eask clean all`。 ** 測試檔案結構** ``` javascript const { TestContext } = require("./helpers"); describe("emacs", () => { const ctx = new TestContext("./test/jest/empty"); beforeAll(async () => await ctx.runEask("clean all")); afterAll(() => ctx.cleanUp); test("eask emacs --version", async () => { await ctx.runEask("emacs --version"); }); test("eask emacs --batch --eval", async () => { await ctx.runEask( 'emacs --batch --eval "(require (quote ert))" --eval "(ert-deftest mytest () (should-not (display-graphic-p)))" -f ert-run-tests-batch', ); }); }); ``` 在 Jest 中,您可以使用 `describe` 將相關的測試分組。在相同的 `describe` 區塊中的測試可以共用設定/停用程式碼,可以作為一個群組停用,並在輸出中被歸類在相同的標題下。 `describe` 區塊可以嵌套在其他的 `describe` 區塊中。 當測試在不同目錄中執行時,或配合 "given, when, then" 的測試風格時,加入巢狀的 `describe` 是個好主意。 您應該為每個測試目錄建立新的 `TestContext` 物件。 所有的 `runEask` 指令都會使用 `TestContext` 的工作目錄。 Jest 的測試在 `test` 區塊中。請注意 `it` 是 `test` 的別名。 測試可以在程式碼中選擇性地停用,就像這樣: - `test.only(name,fn)` 只執行檔案中的測試 - `test.skip(name,fn)` 跳過執行測試,但仍會列印其名稱 - `test.failing(name, fn)` 反轉測試的意義:它*應該*失敗。 `expect` API 以不同的方式匹配值,通常會列印差異作為失敗報告的一部分。 更多資訊請參閱 Jest 的 [expect()](https://jestjs.io/docs/expect) API。 在 `test` 區塊中拋出的未捕獲錯誤會使它失敗並報告錯誤。 這就是為什麼許多測試沒有 `expect` 呼叫,它們只是檢查指令是否成功。 來自 `runEask` 的輸出會包裝在提供一些轉換方法的輔助類 `CommandOutput` 中。 例如,如果您有 `const out = await ctx.runEask("analyze");`,那麼 - `out.combined()` 將 stdout 和 stderr 匯整為一個字串、 - `out.raw()` 返回一個只有`stdout`和`stderr`屬性的純物件、 - `out.sanitized()` 會取代所有符合上下文路徑的絕對路徑。 由於這個類別包覆了 Node 的 `exec()` 方法的輸出,你仍然可以存取 `stdout` 和 `stderr`: ``` javascript const { stderr, stdout } = await ctx.runEask("analyze"); ``` 有些指令會建立檔案或目錄,這些檔案或目錄應該在測試執行後移除。 例如,`eask generate ignore elisp` 會建立一個 `.gitignore` 檔案。 您可以使用 context 的 `removeFiles` 方法來移除相對於 context 路徑的檔案和目錄: ``` javascript describe("Generating", () => { beforeAll(async () => await ctx.removeFiles(".gitignore")); afterAll(async () => await ctx.removeFiles(".gitignore")); it("eask generate ignore elisp", async () => { await ctx.runEask("generate ignore elisp"); }); }); ``` 請注意,`removeFiles()` 會遞迴移除目錄,但不接受模式。 因此,要移除 `./test` 中的所有檔案,只需呼叫 `ctx.remove("test")`。 您可以在一次呼叫中傳入多個檔案或目錄名稱: `ctx.remove("test", ".gitignore")`. 使用 `TestContext.cleanUp()` 立即中止在該上下文中呼叫的任何仍在執行的指令。 如果 Jest 在測試執行後報告"偵測到開啟的句柄",請使用此功能。 請注意,`cleanUp` 會傳送一個信號給所有使用上下文的 `runEask` 指令啟動的進程。 如果在 `afterEach` 鈎結中使用 (即每次測試後),可能會導致失敗。 ### 🪧 快照 [快照測試](https://jestjs.io/docs/snapshot-testing) 將測試的輸出與預期輸出的儲存副本進行比對。 例如: ``` javascript test("eask analyze", async () => { const res = await ctx.runEask("analyze"); expect(res.raw()).toMatchSnapshot(); }); ``` 第一次執行時,Jest 會建立一個新的快照,儲存在鄰近的 `__snapshot__` 目錄中。 您應該將此檔案加入版本控制,因為它是測試的關鍵部分。 如果快照變更,您可以使用選項 `-u` 執行 Jest 來更新快照,例如 `npm run test -- -u` 會更新所有變更的快照。 任何類型的輸出都可以用於快照測試。您可以在變更檔案後快照檔案內容 ``` javascript test("eask analyze", async () => { await ctx.runEask("foo"); const file = ctx.fileContents("Easkfile"); // 檔案為字串 expect(file).toMatchSnapshot(); }); ``` 通常快照會包含會隨時間或環境改變的資料,例如時間戳記或檔案路徑。 `eask analyze` 的快照包含絕對檔案路徑,這些路徑在每台機器上都會不同。 從 `runEask` 輸出的內容會包裝在提供一些轉換方法的輔助類 `CommandOutput` 中。 最簡單的只是移除絕對檔案路徑: ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res.sanitized() // 以 "~" 取代絕對路徑的 CommandOutput 物件 .raw(); // 適合快照的物件 { stderr、stdout } expect(resClean).toMatchSnapshot(); }); ``` 您可以加入自訂的取代函數。在這裡,數字會被 `"x"` 取代。 字串 `"x:x"` 將被 `"y"` 取代。 ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res .sanitized( (x) => x.replace(/[0-9]+/g, "x"), (x) => x.replaceAll("x:x", "y"), ) .raw(); expect(resClean).toMatchSnapshot(); }); ``` 請務必使用 `g` regex 標記,這樣所有匹配的出現都會被取代,或者您也可以使用 `replaceAll`。 使用者提供的函數會在預設的 sanitize 函數之外執行,並且會依給定的順序執行。 ### ⏱️ 超時 T這裡有兩個超時設定,一個用於 Jest,另一個用於 Node 的 `exec()`。 所有逾時值的單位都是毫秒。 由於 `exec()` 超時會立即終止執行中的指令並報告輸出,因此使用它來代替 Jest 的超時會更好。 若要變更單一指令的逾時時間 ``` javascript ctx.runEask("analyze", { timeout: 10000}) ``` 若要變更單次執行的全局逾時,請使用 env var ``` shell env TIMEOUT=30000 npm run test ``` 若要永久變更全局超時,請在 `./helpers.js` 中設定預設值。 如果您變更任一全域逾時時間,**請在 `package.json` 中設定全域逾時時間,以確保全域 Jest 逾時時間較長**。 ``` json "jest": { "rootDir": "./test/jest", "testTimeout": 40000 } ``` ### 📜 樣式 以下是一些測試指令的常見模式。 每種模式都假設 `ctx` 是一個 `TestContext` 物件。 **檢查指令是否成功:** ``` javascript test("eask analyze", async () => { await ctx.runEask("analyze"); }); ``` 在 `test` 區塊中拋出的未捕獲錯誤將使測試失敗,並會報告錯誤。 失敗的指令會包含 stderr 和 stdout。 **檢查命令失敗:** ``` javascript test("eask analyze", async () => { await expect(ctx.runEask("analyze")).rejects.toThrow(); }); ``` **檢查指令是否以特定代碼失敗:** ``` javascript test("eask link add should error", async () => { // 錯誤物件的屬性代碼應該是 1 await expect(ctx.runEask("link add")).rejects.toMatchObject({ code: 1, }); }); ``` ** 檢查指令是否產生某些輸出:** ``` javascript test("eask analyze", async () => { const out = await ctx.runEask("analyze"); expect(out.stderr).toMatch("success"); // 應顯示為子字串 // 如果您要同時檢查 `stderr` 和 `stdout`,只要將它們串連即可 expect(out.stdout + "/n" + out.stderr).toMatch("success"); // 使用輔助方法也是一樣 expect(out.combined()).toMatch("success"); }); ``` **根據快照檢查指令輸出:** 簡單的輸出比對 ``` javascript test("eask analyze", async () => { const res = await ctx.runEask("analyze"); expect(res).toMatchSnapshot(); }); ``` 更新所有已變更的快照: `npm run test -- -u` 移除輸出中的絕對檔案路徑: ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res.sanitized() // 以 "~" 取代絕對路徑的 CommandOutput 物件 .raw(); // 適合快照的物件 { stderr、stdout } expect(resClean).toMatchSnapshot(); }); ``` 套用自訂的轉換來消毒輸出: ``` javascript it("matches snapshot", async () => { const res = await ctx.runEask("analyze"); const resClean = res .sanitized( (x) => x.replace(/[0-9]+/g, "x"), (x) => x.replace(/x:x/g, "y"), ) .raw(); expect(resClean).toMatchSnapshot(); }); ``` 使用者提供的函數會在預設的 sanitize 函數之外執行,並且會按照所給予的順序執行。 ** 修改使用者環境的指令:** 例如,使用 `-c` 或 `-g` 選項的指令。 ``` javascript const { testUnsafe } = require('./helpers'); // 只有當 ALLOW_UNSAFE 為 != 0 時,才會執行此指令。 testUnsafe("global install", async () => { // 這會安裝在 ~/.eask 中,並變更 ~/Eask await ctx.runEask("install -g foo"); }); ``` ### 🩺 常見問題 - 使用 `runEask()` 時,只傳 Eask *arguments*,不傳 `eask` 指令本身。 - 總是 `await` 任何觸發指令的表達式。 - 當使用`expect(...).rejects`時,它應該被 awaited,以便承諾在測試完成前拒絕。 - `TestContext` 的資料夾參數應該相對於專案根目錄,如果它不存在,您可能會得到一個錯誤`ENOENT`。 - 如果您收到 Jest 回報打開句柄的錯誤,那麼請嘗試使用 `afterAll(() => ctx.cleanUp())` - 有兩個逾時值:一個用於 Jest (在 `package.json` 中設定),另一個用於 `node.exec`,在 `./helpers.js` 中透過 env var 設定。 `node.exec` 的逾時設定比 Jest 的低,所以改變測試的逾時值或 `jest.setTimeout` 通常不會有影響。 取而代之,在指令本身設定超時 `runEask("eask emacs", { timeout: 100000 })`。 [Build from source]: https://emacs-eask.github.io/zh-tw/Getting-Started/Install-Eask/#-%e5%be%9e%e5%8e%9f%e5%a7%8b%e7%a2%bc%e6%a7%8b%e5%bb%ba [Why JS?]: https://emacs-eask.github.io/zh-tw/FAQ/#-%E7%82%BA%E4%BB%80%E9%BA%BC%E9%81%B8%E6%93%87-javascript [Node.js]: https://nodejs.org/en/ [npm]: https://www.npmjs.com/ [yargs]: https://github.com/yargs/yargs [Emacs]: https://www.gnu.org/software/emacs/ [Jest]: https://jestjs.io ================================================ FILE: docs/content/Contributing/Documentation/_index.en.md ================================================ --- title: ✒️ Documentation weight: 30 --- {{< toc >}} Eask includes a comprehensive user guide. Please try to extend it accordingly while you implement new features. The documentation is written in [Markdown](https://gohugo.io/), using [Hugo]() and GitHub Pages. The former is the static site generator, and the latter is the static web pages hosting service from GitHub. {{< hint info >}} 💡 You can find all our documentation under the **docs/content/** folder. {{< /hint >}} ## 🚩 Prerequisites To make changes to documentation, you should have: * [hugo](https://gohugo.io/getting-started/quick-start/#step-1-install-hugo) executable; the static site generator. ## 📐 Setup To set up the website locally, you need to first install the theme: ```sh # Clone the repository with submodules... git clone https://github.com/emacs-eask/cli --recurse-submodules # Navgiate to `docs/theme/geekdoc` folder cd ./docs/theme/geekdoc/ # Build the themes npm install && npm run build ``` Then run the `hugo` command: ```sh # Navigate back to `docs` folder cd ./docs/ # Run hugo server locally hugo server ``` You should see something similar to the following screen: ```console Start building sites … hugo v0.148.1-98ba786f2f5dca0866f47ab79f394370bcb77d2f windows/amd64 BuildDate=2025-07-11T12:56:21Z VendorInfo=gohugoio │ EN │ ZH - TW ──────────────────┼─────┼───────── Pages │ 36 │ 34 Paginator pages │ 0 │ 0 Non-page files │ 2 │ 0 Static files │ 144 │ 144 Processed images │ 0 │ 0 Aliases │ 2 │ 1 Cleaned │ 0 │ 0 Built in 3987 ms Environment: "development" Serving pages from disk Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) Press Ctrl+C to stop ``` And that's it! Now you can open the browser with the URL `localhost:1313`. 🎉 {{< hint info >}} 💡 You can specify **-D** option if you consider writing a draft. {{< /hint >}} ================================================ FILE: docs/content/Contributing/Documentation/_index.zh-tw.md ================================================ --- title: ✒️ 文檔 weight: 30 --- {{< toc >}} Eask 包含全面的用戶指南。 請嘗試相應地擴展它您實施新功能。 該文檔使用 [Hugo]() 和 GitHub Pages 以 [Markdown](https://gohugo.io/) 編寫。 前者是靜態站點生成器,後者是靜態網頁託管服務來自 GitHub。 {{< hint info >}} 💡 您可以在 **docs/content/** 文件夾下找到我們所有的文檔。 {{< /hint >}} ## 🚩 必備條件 要更改文檔,您應該: - [hugo](https://gohugo.io/getting-started/quick-start/#step-1-install-hugo) 可執行; 靜態站點生成器。 ## 📐 設置 要在本地設置網站,您需要先安裝主題: ```sh # 克隆代碼庫和子模塊一起... git clone https://github.com/emacs-eask/cli --recurse-submodules # 導航到 `docs/theme/geekdoc` 文件夾 cd ./docs/theme/geekdoc/ # 構建主題 npm install && npm run build ``` 然後運行 `hugo` 命令: ```sh # 導航回 `docs` 文件夾 cd ./docs/ # 在本地運行 hugo 服務器 hugo server ``` 您應該會看到類似以下畫面: ```console Start building sites … hugo v0.148.1-98ba786f2f5dca0866f47ab79f394370bcb77d2f windows/amd64 BuildDate=2025-07-11T12:56:21Z VendorInfo=gohugoio │ EN │ ZH - TW ──────────────────┼─────┼───────── Pages │ 36 │ 34 Paginator pages │ 0 │ 0 Non-page files │ 2 │ 0 Static files │ 144 │ 144 Processed images │ 0 │ 0 Aliases │ 2 │ 1 Cleaned │ 0 │ 0 Built in 3987 ms Environment: "development" Serving pages from disk Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) Press Ctrl+C to stop ``` 就是這樣! 現在您可以在瀏覽器裡面打開 `localhost:1313`。 🎉 {{< hint info >}} 💡 如果你考慮寫草稿,你可以指定 **-D** 選項。 {{< /hint >}} ================================================ FILE: docs/content/Contributing/How-to-Contribute/_index.en.md ================================================ --- title: ❓ How to Contribute weight: 0 --- {{< toc >}} ## ⚜️ Code of Conduct We have adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read the full text so that you can understand what actions will and will not be tolerated. ## 🪑 Open Development All work on Eask happens directly on [GitHub](https://github.com/emacs-eask/cli). Both core team members and external contributors send pull requests which go through the same review process. ## 📌 Semantic Versioning Eask follows [semantic versioning](https://semver.org/). We release patch versions for critical bugfixes, minor versions for new features or non-essential changes, and major versions for any breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance. Every significant change is documented in the [changelog file](https://github.com/emacs-eask/cli/blob/master/CHANGELOG.md). ## 💡 Branch Organization Submit all changes directly to the `master` branch. We don’t use separate branches for development or for upcoming releases. We do our best to keep `master` in good shape, with all tests passing. Code that lands in `master` must be compatible with the latest stable release. It may contain additional features, but no breaking changes. We should be able to release a new minor version from the tip of `master` at any time. ## 📂 State of the project The project's bare-bones are pretty much done, we are currently looking for contributors to give us feedback and improve our TUI/UX for this tool! We are also looking for advice to add more. Emacser often use commands and options, so these features are prepared by default! Like command `lint` (package-lint) or option `--debug` refers to `debug-on-error` to `t`! ================================================ FILE: docs/content/Contributing/How-to-Contribute/_index.zh-tw.md ================================================ --- title: ❓ 如何貢獻 weight: 0 --- {{< toc >}} ## ⚜️ 行為守則 我們採用了[貢獻者公約](https://www.contributor-covenant.org/) 作為其行為準則,我們希望項目參與者遵守它。請閱讀全文,以便您了解將要執行的操作不容忍。 ## 🪑 開放發展 Eask 上的所有工作都直接在 [GitHub](https://github.com/emacs-eask/cli) 上進行。 核心團隊成員和外部貢獻者都發送 PR 通過相同的審查程序。 ## 📌 語義版本控制 Eask 遵循 [語義版本控制](https://semver.org/)。 我們發布補丁版本對於關鍵錯誤修復、 新功能的次要版本或非必要的更改,以及任何重大更改的主要版本。 當我們進行重大更改時,我們 還在次要版本中引入棄用警告,以便我們的用戶了解關於即將發生的變化並提前遷移他們的代碼。 [變更日誌文件](https://github.com/emacs-eask/cli/blob/master/CHANGELOG.md) 中記錄了每個重大變更。 ## 💡 分支機構 將所有更改直接提交到 `master` 分支。 我們不使用單獨的分支機構用於開發或即將發布的版本。 我們盡最大努力讓 `master` 保持良好狀態,通過所有測試。 落在 `master` 中的代碼必須與最新的穩定版本兼容。 它可能包含附加功能,但沒有重大更改。 我們應該能夠釋放隨時從 `master` 的提示中獲取一個新的次要版本。 ## 📂 項目狀況 該項目的基本框架已經完成,我們目前正在尋找貢獻者向我們提供反饋並改進此工具的 TUI/UX! 我們也在尋求建議以增加更多。 Emacser 常用命令和選項,所以這些功能是默認准備的! 點贊命令 `lint` (package-lint) 或選項 `--debug` 指的是 `debug-on-error` 到 `t`! ================================================ FILE: docs/content/Contributing/PR/_index.en.md ================================================ --- title: 📭 Pull Request weight: 40 --- If all tests have passed, and the Eask can operate normally with updated documentation (if any), please send us a [pull request](https://github.com/emacs-eask/cli/pulls) with your changes. 🎊 # 🧪 Tests Explained This has moved to the repository [README.md](https://github.com/emacs-eask/cli/blob/master/README.md) file. Please visit https://github.com/emacs-eask/cli#-testing. ================================================ FILE: docs/content/Contributing/PR/_index.zh-tw.md ================================================ --- title: 📭 提交 PR weight: 40 --- 如果所有測試都通過,Eask 可以在更新後正常運行文檔(如果有),請向我們發送 [pull request](https://github.com/emacs-eask/cli/pulls) 隨著你的改變。 🎊 # 🧪 測試說明 這已移至存儲庫 [README.md](https://github.com/emacs-eask/cli/blob/master/README.md) 文件。 請訪問 https://github.com/emacs-eask/cli#-testing。 ================================================ FILE: docs/content/Contributing/_index.en.md ================================================ --- title: Contributing weight: 500 --- ================================================ FILE: docs/content/Contributing/_index.zh-tw.md ================================================ --- title: 貢獻 weight: 500 --- ================================================ FILE: docs/content/DSL/_index.en.md ================================================ --- title: Domain Specific Language weight: 200 --- This document provides a reference on the [DSL](https://en.wikipedia.org/wiki/Domain-specific_language). {{< toc >}} # 🚩 Package metadata ## 🔍 **package** (`name` `version` `description`) Declare a package with the given name, version, and description: ```elisp (package "ert-runner" "0.7.0" "Opinionated Ert testing workflow") ``` All arguments are strings. The version must be a version understood by Emacs' built-in `version-to-list`. ## 🔍 **website-url** (`url`) Declare the package website. ```elisp (website-url "https://github.com/owner/repo.git") ``` ## 🔍 **keywords** (`&rest keywords`) Declare package keywords. ```elisp (keywords "tool" "utility" "emacs") ``` ## 🔍 **author** (`name` &optional `email`) Declare package's author. ```elisp (author "USER NAME" "user.name@example.com") ``` ## 🔍 **license** (`name`) Declare package's author. ```elisp (license "GPLv3") ``` # 🚩 Package contents ## 🔍 **package-file** (`file` `version` `description`) Define this package and its runtime dependencies from the package headers of a file (used only for package development). ```elisp (package-file "foo.el") ``` ## 🔍 **package-descriptor** (`pkg-file`) Declare all package metadata directly by specifying a package descriptor contained in file with name given by file. ```elisp (package-descriptor "foo-pkg.el") ``` ## 🔍 **files** (`&rest patterns`) Specify list of files that are included in this project. ```elisp (files "foo.el") (files "*.el" "core/*.el") ``` # 🚩 Tests ## 🔍 **script** (`name` `command` &rest `args`) Add built-in scripts and their preset life cycle event as well as arbitrary scripts. ```elisp (script "test" "echo This is a test!") ``` # 🚩 Dependencies ## 🔍 **source** (`alias`) ## 🔍 **source** (`name` `url`) Add a package archive to install dependencies from. ```elisp (source "gnu") (source "gnu" "https://elpa.gnu.org/packages/") ``` Available aliases: - `gnu` (https://elpa.gnu.org/packages/) - `nongnu` (https://elpa.nongnu.org/nongnu/) - `celpa` (https://celpa.conao3.com/packages/) - `eine` (https://emacs-eine.github.io/elpa/packages/) - `jcs-elpa` (https://jcs-emacs.github.io/jcs-elpa/packages/) - `marmalade` (https://marmalade-repo.org/packages/) - `melpa` (https://melpa.org/packages/) - `melpa-stable` (https://stable.melpa.org/packages/) - `org` (https://orgmode.org/elpa/) - `shmelpa` (https://shmelpa.commandlinesystems.com/packages/) - `ublt` (https://elpa.ubolonton.org/packages/) Available `devel` aliases: - `gnu-devel` (https://elpa.gnu.org/devel/) - `nongnu-devel` (https://elpa.nongnu.org/nongnu-devel/) {{< hint ok >}} 💡 Use **--insecure** to make **https** to **http**, but not recommended! {{< /hint >}} ## 🔍 **source-priority** (`name` `priority`) Set archive priority. ```elisp (source-priority "gnu" 5) ``` ## 🔍 **depends-on** (`package-name` `&optional minimum-version`) ## 🔍 **depends-on** (`package-name` `&rest recipe`) Specify a dependency of this package. Specify dependencies that are listed in **archives**: ```elisp (depends-on "emacs" "26.1") (depends-on "dash") (depends-on "company") ``` Specify dependencies in **file** format: ```elisp (depends-on "auto-rename-tag" :file "/path/to/auto-rename-tag") (depends-on "lsp-ui" :file "/path/to/lsp-ui") ``` Specify dependencies in **vc** format: ```elisp (depends-on "auto-rename-tag" :vc "jcs-elpa/auto-rename-tag") (depends-on "lsp-ui" :vc "emacs-lsp/lsp-ui") ``` Specify dependencies in **try** format: ```elisp (depends-on "auto-rename-tag" :try "https://raw.githubusercontent.com/emacs-vs/auto-rename-tag/refs/heads/master/auto-rename-tag.el") (depends-on "lsp-ui" :try) ; Try it, don't install it. ``` Specify dependencies in **recipe** format: ```elisp (depends-on "auto-rename-tag" :fetcher 'github :repo "jcs-elpa/auto-rename-tag") (depends-on "lsp-ui" :fetcher 'github :repo "emacs-lsp/lsp-ui" :files '(:defaults "lsp-ui-doc.html" "resources")) ``` {{< hint ok >}} 💡 Install dependencies with command **eask install-deps**! {{< /hint >}} ## 🔍 **development** (`&rest body`) Scope all `depends-on` expressions in body to development. ```elisp (development (depends-on "ert-runner") (depends-on "elsa")) ``` {{< hint ok >}} 💡 You would need to specify the **--dev** option for development dependencies! {{< /hint >}} ## 🔍 **load-paths** (`&rest paths`) Specify paths to add to `load-path`. ```elisp (load-paths "/lisp/") ``` ## 🔍 **exec-paths** (`&rest paths`) Specify paths to add to `exec-path`. ```elisp (load-paths "/bin/") ``` ================================================ FILE: docs/content/DSL/_index.zh-tw.md ================================================ --- title: 领域特定语言 weight: 200 --- 本文檔是關於 [DSL] (https://en.wikipedia.org/wiki/Domain-specific_language)。 {{< toc >}} # 🚩 包元資料 ## 🔍 **package** (`name` `version` `description`) 使用給定的名稱、版本和描述聲明一個包: ```elisp (package "ert-runner" "0.7.0" "Opinionated Ert testing workflow") ``` 所有參數都是字符串。 該版本必須是 Emacs 內置的 `version-to-list` 可以理解的版本。 ## 🔍 **website-url** (`url`) 聲明包網站。 ```elisp (website-url "https://github.com/owner/repo.git") ``` ## 🔍 **keywords** (`&rest keywords`) 聲明包關鍵字。 ```elisp (keywords "tool" "utility" "emacs") ``` ## 🔍 **author** (`name` &optional `email`) 聲明包的作者。 ```elisp (author "使用者名稱" "user.name@example.com") ``` ## 🔍 **license** (`name`) 聲明包的作者。 ```elisp (license "GPLv3") ``` # 🚩 Package contents ## 🔍 **package-file** (`file` `version` `description`) 從文件的包頭定義此包及其運行時依賴項(僅用於包開發)。 ```elisp (package-file "foo.el") ``` ## 🔍 **package-descriptor** (`pkg-file`) 通過指定文件中包含的包描述符直接聲明所有包元數據,名稱由文件給出。 ```elisp (package-descriptor "foo-pkg.el") ``` ## 🔍 **files** (`&rest patterns`) 指定包含在此項目中的文件列表。 ```elisp (files "foo.el") (files "*.el" "core/*.el") ``` # 🚩 測試 ## 🔍 **script** (`name` `command` &rest `args`) 添加內置腳本及其預設的生命週期事件以及任意腳本。 ```elisp (script "test" "echo This is a test!") ``` # 🚩 依賴 ## 🔍 **source** (`alias`) ## 🔍 **source** (`name` `url`) 添加包存檔以從中安裝依賴項。 ```elisp (source "gnu") (source "gnu" "https://elpa.gnu.org/packages/") ``` 可用別名: - `gnu` (https://elpa.gnu.org/packages/) - `nongnu` (https://elpa.nongnu.org/nongnu/) - `celpa` (https://celpa.conao3.com/packages/) - `eine` (https://emacs-eine.github.io/elpa/packages/) - `jcs-elpa` (https://jcs-emacs.github.io/jcs-elpa/packages/) - `marmalade` (https://marmalade-repo.org/packages/) - `melpa` (https://melpa.org/packages/) - `melpa-stable` (https://stable.melpa.org/packages/) - `org` (https://orgmode.org/elpa/) - `shmelpa` (https://shmelpa.commandlinesystems.com/packages/) - `ublt` (https://elpa.ubolonton.org/packages/) 可用 `devel` 別名: - `gnu-devel` (https://elpa.gnu.org/devel/) - `nongnu-devel` (https://elpa.nongnu.org/nongnu-devel/) {{< hint ok >}} 💡 使用**--insecure**讓**https**轉**http**,但不推薦! {{< /hint >}} ## 🔍 **source-priority** (`name` `priority`) 設置 archive 優先級。 ```elisp (source-priority "gnu" 5) ``` ## 🔍 **depends-on** (`package-name` `&optional minimum-version`) ## 🔍 **depends-on** (`package-name` `&rest recipe`) 指定此包的依賴項。 指定 **archives** 中列出的依賴項: ```elisp (depends-on "emacs" "26.1") (depends-on "dash") (depends-on "company") ``` 以 **file** 格式指定依賴項: ```elisp (depends-on "auto-rename-tag" :file "/path/to/auto-rename-tag") (depends-on "lsp-ui" :file "/path/to/lsp-ui") ``` 以 **vc** 格式指定依賴項: ```elisp (depends-on "auto-rename-tag" :vc "jcs-elpa/auto-rename-tag") (depends-on "lsp-ui" :vc "emacs-lsp/lsp-ui") ``` 以 **try** 格式指定依賴項: ```elisp (depends-on "auto-rename-tag" :try "https://raw.githubusercontent.com/emacs-vs/auto-rename-tag/refs/heads/master/auto-rename-tag.el") (depends-on "lsp-ui" :try) ; 只有試看看, 不安裝. ``` 以 **recipe** 格式指定依賴項: ```elisp (depends-on "auto-rename-tag" :fetcher 'github :repo "jcs-elpa/auto-rename-tag") (depends-on "lsp-ui" :fetcher 'github :repo "emacs-lsp/lsp-ui" :files '(:defaults "lsp-ui-doc.html" "resources")) ``` {{< hint ok >}} 💡 使用命令 **eask install-deps** 安裝依賴項! {{< /hint >}} ## 🔍 **development** (`&rest body`) 將正文中所有 `depends-on` 表達式的範圍限定為開發依賴。 ```elisp (development (depends-on "ert-runner") (depends-on "elsa")) ``` {{< hint ok >}} 💡 您需要為開發依賴項指定 **--dev** 選項! {{< /hint >}} ## 🔍 **load-paths** (`&rest paths`) 指定要添加到 `load-path` 的路徑。 ```elisp (load-paths "/lisp/") ``` ## 🔍 **exec-paths** (`&rest paths`) 指定要添加到 `exec-path` 的路徑。 ```elisp (load-paths "/bin/") ``` ================================================ FILE: docs/content/Development-API/_index.en.md ================================================ --- title: Development API weight: 700 --- This document provides a reference to the public Eask API, which you may use in your projects and extensions to Eask. {{< toc >}} # 🚩 Entry Point ## 🔍 Snippet: _prepare.el Load `lisp/_prepare.el` to start using other Eask API. ```elisp (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ``` Each Elisp scripts should have this snippet at the very top of the file. ## 🔍 Macro: eask-start (&rest `body`) Command entry point. Each command file should contain this macro somewhere in the file. ```elisp (eask-start ;; TODO: design your command here! ) ``` # 🚩 Environment ## 🔍 Variable: eask-has-colors Return non-nil if the terminal supports colors. ```elisp (when eask-has-colors ... ``` ## 🔍 Variable: eask-homedir Eask's home directory path. ```elisp (message "%s" eask-homedir) ; ~/.eask/ ``` ## 🔍 Variable: eask-userdir Eask's user directory path. ```elisp (message "%s" eask-userdir) ; ~/ ``` ## 🔍 Variable: eask-package-sys-dir Eask global elpa directory; it will be treated as the system-wide packages. ```elisp (message "%s" eask-package-sys-dir) ; ~/.eask/30.2/elpa/ ``` ## 🔍 Variable: eask-invocation Eask's invocation program path. ```elisp (message "%s" eask-invocation) ``` It could be the `node` executable or the `eask` executable itself. ## 🔍 Variable: eask-is-pkg Return non-nil if Eask is packaged. ```elisp (when eask-is-pkg ... ``` ## 🔍 Variable: eask-rest Eask's arguments after command separator `--'; return a list. ```sh eask -- args0 args1 ``` Output: ```elisp (message "%s" eask-rest) ; '(args0 args1) ``` ## 🔍 Function: eask-rest () Eask's arguments after command separator `--'; return a string. ```sh eask -- args0 args1 ``` Output: ```elisp (message "%s" (eask-rest)) ; "args0 args1" ``` # 🚩 Core ## 🔍 Variable: eask-lisp-root Points to `lisp` directory from the project root. ```elisp (message "%s" eask-lisp-root) ; path/to/eask/cli/lisp/ ``` ## 🔍 Function: eask-working-directory () Return the working directory of the program going to be executed. ```elisp (message "%s" (eask-working-directory)) ; path/to/current/work/space/ ``` ## 🔍 Function: eask-command () Return the current command in string. Suppose the command is: ```sh eask init ``` then, ```elisp (message "%s" (eask-command)) ; init ``` ## 🔍 Function: eask-command-check (`version`) Report error if the current command requires minimum `version`. ```elisp (eask-command-check "27.1") ; The command requires 27.1 and above! ``` ## 🔍 Function: eask-command-p (`commands`) Return t if COMMANDS is the current command. ## 🔍 Function: eask-special-p () Return `t` if the command that can be run without Eask-file existence. This allows some commands can still be executed without defining the user directory. This can be handy when you want to do normal operations without touching the user directory. ## 🔍 Function: eask-execution-p () Return `t` if the command is the execution command. This is added because we don't want to pollute `error` and `warn` functions. ## 🔍 Function: eask-checker-p () Return `t` if running Eask as the checker. Without this flag, the process will be terminated once the error is occurred. This flag allows you to run through operations without reporting errors. ## 🔍 Function: eask-script (`script`) Return full script filename. ```elisp (eask-script "extern/pacakge") ; {project-root}/lisp/extern/package.el ``` ## 🔍 Function: eask-load (`script`) Load another eask script. ```elisp (eask-load "extern/ansi") ; load {project-root}/lisp/extern/ansi.el file ``` ## 🔍 Function: eask-call (`script`) Call another eask script. ```elisp (eask-call "clean/elc") ; call command `eask clean-elc` ``` {{< hint info >}} 💡 We don't often call this since we don't wish to execute another command directly! {{< /hint >}} ## 🔍 Function: eask-import (`url`) Load and evaluate the script from the url. ```elisp (eask-import "https://raw.githubusercontent.com/emacsmirror/emacswiki.org/master/yes-no.el") ;; The script will be available after the call! ``` ## 🔍 Macro: eask-defvc< (`version` &rest `body`) Define the scope if the Emacs version is below a specific version. `VERSION` is an integer and will be compared with `emacs-major-version`. ```elisp (eask-defvc< 28 ;; This is missing before Emacs 28; define it (defvar package-native-compile nil)) ``` {{< hint info >}} 💡 This is used for Emacs compatibility! {{< /hint >}} ## 🔍 Macro: eask--silent (&rest `body`) Mute all messages from standard output inside the scope. ```elisp (eask--unsilent (message "You can't hear me! :(")) ``` ## 🔍 Macro: eask--unsilent (&rest `body`) Unmute all messages from standard output inside the scope. ```elisp (eask--unsilent (message "You can hear me! :)")) ``` ## 🔍 Function: eask-dependencies () Return a list of dependencies. Elements should either be `(NAME . VERSION)` or `(NAME . RECIPE-FORMAT)`. ## 🔍 Function: eask-pkg-init (&optional `force`) Initialize packages for use. ```elisp (eask-start (eask-pkg-init) ;; Now you can use packages installed in `package-user-dir' ) ``` {{< hint info >}} 💡 This is usually called after **eask-start**! {{< /hint >}} ## 🔍 Macro: eask-with-archives (`archives` &rest `body`) Scope that temporary makes archives available. The argument `ARCHIVES` can either be a string or a list of strings. ```elisp (eask-with-archives "melpa" (eask-package-install 'dash)) ; install packages that are only defined in MELPA ``` {{< hint info >}} 💡 This is handy when you need certain packages from certain archives. {{< /hint >}} ## 🔍 Function: eask-archive-install-packages (`archives` &rest `names`) Install packages with archives setup. The arugment `names` can be a symbol or list of symbols. ```elisp (eask-archive-install-packages '("gnu" "melpa") 'el2org) ; Accept multiple arguments. ``` {{< hint info >}} 💡 This only installs packages if they are missing. {{< /hint >}} ## 🔍 Function: eask-package-desc (`name` &optional `current`) Build package descriptor for a package. `CURRENT` means installed packages; otherwise it will return any available packages from selected package archives. ## 🔍 Function: eask-argv (`index`) Return a command-line argument by index. ## 🔍 Function: eask-args () Return a list that is extracted from command-line arguments. ```sh eask info --verbose 4 foo bar ``` It will ignore `--verbose` and `4`, and only returns `foo`, and `bar`. ## 🔍 Variable: eask-file Path to currently loaded Eask-file. ## 🔍 Variable: eask-file-root Directory to currently loaded Eask-file. ## 🔍 Function: eask--match-file (`name`) Check to see if NAME is our target Eask-file, then return it. The following output is with Emacs 28.1: ```elisp (eask--match-file "Eask") ; t (eask--match-file "Eask.28") ; t (eask--match-file "Eask.28.1") ; t (eask--match-file "Eask.29") ; nil (eask--match-file "Easkfile") ; t (eask--match-file "Easkfile.28") ; t (eask--match-file "Easkfile.29") ; nil ``` ## 🔍 Function: eask--all-files (&optional `dir`) Return a list of Eask files from DIR. Consider the following directory tree: ```text . root ├── Eask ├── Eask.28 └── Eask.29 ``` The following output is with Emacs 28.1: ```elisp (eask--all-files "/root/") ; '(Eask Eask.28) ``` ## 🔍 Function: eask--find-files (`start-path`) Find the Eask-file from START-PATH. Consider the following directory tree: ```text .project ├─ src │ └── config.el ├── Eask ├── Eask.28 └── Eask.29 ``` The following output is with Emacs 28.1: ```elisp (eask--find-files "/project/src/config.el") ; '(/project/Eask /project/Eask.28) ``` ## 🔍 Function: eask-file-try-load (`start-path`) Try load the Eask-file in START-PATH. ```elisp (eask--find-files "/project/src/") ; t ``` ## 🔍 Function: eask-network-insecure-p () Return `t` if the current Emacs session allows insecure network connections. # 🚩 Flags ## 🔍 Function: eask-global-p () Return `t` if the `global` option is enabled. ```elisp (when (eask-global-p) user-emacs-directory) ; ~/.eask/ ``` ## 🔍 Function: eask-config-p () Return `t` if the `config` option is enabled. ```elisp (when (eask-config-p) user-emacs-directory) ; ~/.emacs.d ``` {{< hint info >}} 💡 If both options `--config` and `--global` are on, the global space is chosen over the config space. {{< /hint >}} ## 🔍 Function: eask-local-p () This uses the current workspace, and this is the default. ```elisp (when (eask-local-p) user-emacs-directory) ; ./.eask/{emacs-version}/ ``` {{< hint info >}} 💡 This function returns `t` only when `(eask-global-p)` and `(eask-config-p)` are false! {{< /hint >}} ## 🔍 Function: eask-all-p () Return `t` if the `all` option is enabled. ```elisp (when (eask-all-p) ;; Run all tests ...) ``` ## 🔍 Function: eask-quick-p () Return `t` if the `quick` option is enabled. ```elisp (unless (eask-quick-p) (load user-init-file) ...) ``` ## 🔍 Function: eask-force-p () Return `t` if the `force` option is enabled. ```elisp (package-delete .. (eask-force-p)) ``` ## 🔍 Function: eask-dev-p () Return `t` if the `development` option is enabled. ```elisp (when (eask-dev-p) (package-install 'ert-runner)) ; install development dependency ``` ## 🔍 Function: eask-debug-p () Return `t` if the `debug` option is enabled. ```elisp (when (eask-debug-p) (error "Executing in debug mode...")) ``` ## 🔍 Function: eask-strict-p () Return `t` if the `strict` option is enabled. ```elisp (setq byte-compile-error-on-warn (eask-strict-p)) ``` ## 🔍 Function: eask-timestamps-p () Return `t`/`nil` if the `timestamps` option is enabled/disabled. These flags can't co-exist in the same command. ```elisp (when (eask-timestamps-p) (message "Print log with timestamps!")) ``` ## 🔍 Function: eask-log-level-p () Return `t`/`nil` if the `log-level` option is enabled/disabled. These flags can't co-exist in the same command. ```elisp (when (eask-log-level-p) (message "Print log with level prefix!")) ``` ## 🔍 Function: eask-log-file-p () Return `t`/`nil` if the `log-file` option is enabled/disabled. These flags can't co-exist in the same command. ```elisp (when (eask-log-file-p) (message "Let's create a log file!")) ``` ## 🔍 Function: eask-no-color-p () Return `t` if the `color` option is enabled. ```elisp (unless (eask-no-color-p) (message "This string has no ansi code!")) ``` ## 🔍 Function: eask-allow-error-p () Return `t` if the `allow-error` option is enabled. ```elisp (unless (eask-allow-error-p) (error "Stop here.")) ``` ## 🔍 Function: eask-insecure-p () Return `t` if the `insecure` option is enabled. ```elisp (when (eask-insecure-p) ;; Do some dangerous tasks? ) ``` ## 🔍 Function: eask-proxy () ## 🔍 Function: eask-http-proxy () ## 🔍 Function: eask-https-proxy () ## 🔍 Function: eask-no-proxy () Return a **string** represents `hostname` + `port number`. ```sh eask [command] --proxy "localhost:1000" eask [command] --http-proxy "localhost:2000" eask [command] --https-proxy "localhost:3000" eask [command] --no-proxy "localhost:4000" ``` ## 🔍 Function: eask-destination () Return a **string** represents the destination (output path). ```elisp (write-file (or (eask-destination) "./dist")) ; write file to destination ``` ## 🔍 Function: eask-depth () Return an **integer** represents the depth of the current print level. ```elisp (setq print-level (eask-depth)) ``` ## 🔍 Function: eask-verbose () Return an **integer** represents the verbosity level. ```elisp (when (= (eask-verbose) 4) (setq byte-compile-verbose t)) ``` # 🚩 `Eask`-file These functions are the actual implementation of `Eask`-file DSL; and have the word `eask-` as the function prefix. See [DSL](https://emacs-eask.github.io/DSL/) section for more information. ## 🔍 Variable: eask-package It holds package's `NAME`, `VERSION`, and `DESCRIPTION` in a plist. ```elisp (plist-get eask-package :name) ; return package name ``` Three functions that are extended from this variable: - `(eask-package-name)` - `(eask-package-version)` - `(eask-package-description)` ## 🔍 Variable: eask-package-file Points to package main file. ## 🔍 Variable: eask-package-desc Package descriptor from the package main file. ```elisp (package-desc-p eask-package-desc) ; return t ``` {{< hint warning >}} ⚠ This can be **nil** if the package-descriptor cannot be constructed correctly! {{< /hint >}} ## 🔍 Variable: eask-files Holds a list of files pattern in wildcard specification. ## 🔍 Variable: eask-scripts Holds a list of available scripts that can be executed by user using the `eask run-script` command. ## 🔍 Variable: eask-depends-on-emacs Holds information about Emacs minimum version. ```elisp (depends-on "emacs" "26.1") ``` Function will return Emacs version in string. - `(eask-depends-emacs-version)` - return `"26.1"` ## 🔍 Variable: eask-depends-on Holds a list of dependencies. ## 🔍 Variable: eask-depends-on-dev Holds a list of dependencies that are development used. ## 🔍 Function: eask-f-package (`name` `version` `description`) Alias of `package`. ## 🔍 Function: eask-f-website-url (`url`) Alias of `website-url`. ## 🔍 Function: eask-f-keywords (&rest `keywords`) Alias of `keywords`. ## 🔍 Function: eask-f-author (`name` &optional `email`) Alias of `author`. ## 🔍 Function: eask-f-license (`name`) Alias of `license`. ## 🔍 Function: eask-f-package-file (`file`) Alias of `package-file`. ## 🔍 Function: eask-f-files (`pkg` &rest `args`) Alias of `files`. ## 🔍 Function: eask-f-script (`name` `command` &rest `args`) Alias of `script`. ## 🔍 Function: eask-f-source (`name` &optional `location`) Alias of `source`. ## 🔍 Function: eask-f-source-priority (`name` &optional `priority`) Alias of `source-priority`. ## 🔍 Function: eask-f-depends-on (`pkg` &rest `args`) Alias of `depends-on`. ## 🔍 Function: eask-f-development (&rest `dependencies`) Alias of `development`. ## 🔍 Function: eask-f-exec-paths (&rest `dirs`) Alias of `exec-paths`. ## 🔍 Function: eask-f-load-paths (&rest `dirs`) Alias of `load-paths`. # 🚩 Logging Logger utility with timestamps and log level. The log level value is defined in function `eask--verb2lvl`. | Level | Description | Value | |:--------|:----------------------------------------------------------------------------------------------------------|:------| | `debug` | Designates fine-grained informational events that are most useful to debug an application. | 4 | | `log` | Designates normal messages. | 3 | | `info` | Designates informational messages that highlight the progress of the application at coarse-grained level. | 2 | | `warn` | Designates potentially harmful situations. | 1 | | `error` | Designates error events that might still allow the application to continue running. | 0 | The default level is `log`. ## 🔍 Variable: eask-verbosity The verbosity level is represented as an integer. ```elisp (setq eask-verbosity 4) ; you could set from 0 to 4 ``` ## 🔍 Variable: eask-timestamps Log messages with timestamps. ```elisp (setq eask-timestamps t) ``` Output: ```text 2022-04-14 13:44:46 This is a message with timestamps ``` ## 🔍 Variable: eask-log-level Log messages with level. (default: `nil`) ```elisp (setq eask-log-level t) ``` Output: ```text [DEBUG] This is a DEBUG message with log level ``` ## 🔍 Variable: eask-log-file Weather to generate log files. (default: `nil`) ```elisp (setq eask-log-level t) ``` Use command `cat` to see the log, ```text cat /.log/messages.log ``` ## 🔍 Variable: eask-level-color Define each log level color. ```elisp (setq eask-level-color '((debug . ansi-blue) (log . ansi-white) (info . ansi-cyan) (warn . ansi-yellow) (error . ansi-red))) ``` ## 🔍 Function: eask-reach-verbosity-p (`symbol`) Make execution when it reaches the verbosity level. ```elisp (when (eask-reach-verbosity-p 'debug) ;; TODO: execution here.. ) ``` ## 🔍 Macro: eask-with-verbosity (`symbol` &rest `body`) Define verbosity scope. ```elisp (eask-with-verbosity 'debug ;; TODO: execution here.. ) ``` Everything in the scope of this macro will be muted unless the verbosity reaches. It will only be printed when you have specified `--verbose 4` global option. ## 🔍 Macro: eask-with-verbosity-override (`symbol` &rest `body`) Define override verbosity scope. ```elisp (eask-with-verbosity 'debug (eask-with-verbosity-override 'log ;; TODO: execution here.. ) (eask-with-verbosity-override 'info ;; TODO: execution here.. )) ``` Like macro `eask-with-verbosity`; but force display messages if it wasn't able to display. ## 🔍 Function: eask-debug (`msg` &rest `args`) ```elisp (eask-debug "This is DEBUG message") ``` ```text 2022-04-14 17:31:54 [DEBUG] This is DEBUG message ``` ## 🔍 Function: eask-log (`msg` &rest `args`) ```elisp (eask-log "This is LOG message") ``` ```text 2022-04-14 17:31:54 [LOG] This is LOG message ``` ## 🔍 Function: eask-info (`msg` &rest `args`) ```elisp (eask-info "This is INFO message") ``` ```text 2022-04-14 17:31:54 [INFO] This is INFO message ``` ## 🔍 Function: eask-warn (`msg` &rest `args`) ```elisp (eask-warn "This is WARNING message") ``` ```text 2022-04-14 17:31:54 [WARNING] This is WARNING message ``` ## 🔍 Function: eask-error (`msg` &rest `args`) ```elisp (eask-error "This is ERROR message") ``` ```text 2022-04-14 17:31:54 [ERROR] This is ERROR message ``` ## 🔍 Function: eask-print (`msg` &rest `args`) Standard output printing without newline. ```elisp (eask-println "Print to stdout!") ``` ## 🔍 Function: eask-println (`msg` &rest `args`) Like the function `eask-print` but contains the newline at the end. ```elisp (eask-println "Print to stdout! (with newline)") ``` ## 🔍 Function: eask-msg (`msg` &rest `args`) Like the `message` function but will replace unicode with color. ```elisp (eask-msg "Print this message with newline!") ``` ## 🔍 Function: eask-write (`msg` &rest `args`) Like the `eask-msg` function but without the newline at the end. ```elisp (eask-write "Print this message without newline...") ``` ## 🔍 Function: eask-report (&rest `args`) Report error/warning depends on strict flag. ```elisp (eask-report "This can be warning or error") ``` See option [--strict](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#---strict). # 🚩 Exit Code ## 🔍 Variable: eask--exit-code Exit code specification. ## 🔍 Function: eask-exit-code (&optional `key`) Return the exit code by the key symbol in the variable `eask--exit-code`. ```elisp (eask-exit-code 'misuse) ; by symbol ``` ## 🔍 Function: eask--exit (&optional `exit-code` &rest `_`) Send exit code. This will kill Emacs process. ```elisp (eask--exit 2) ; by number (eask--exit 'misuse) ; by symbol ``` # 🚩 Error Handling ## 🔍 Variable: eask--ignore-error-p Non-nil to prevent Emacs from being killed. ```elisp (let ((eask--ignore-error-p t)) (error "Emacs can't die! :P")) ``` ## 🔍 Variable: eask-inhibit-error-message Non-nil to stop error/warning message. ```elisp (let ((eask-inhibit-error-message t)) (error "This won't display at all!")) ``` ## 🔍 Macro: eask-ignore-errors (&rest `body`) Prevent Emacs from being killed. ```elisp (eask-ignore-errors (error "Emacs can't die! :P")) ``` ## 🔍 Macro: eask--silent-error (&rest `body`) Inhibit display error/warning messages. ```elisp (eask--silent-error (error "This won't display at all!")) ``` ## 🔍 Macro: eask-ignore-errors-silent (&rest `body`) Prevent Emacs from being killed and inhibit display error/warning messages. ```elisp (eask-ignore-errors-silent (error "Nothing happens!")) ``` # 🚩 File ## 🔍 Function: eask-package-files () Return a list of package files. ## 🔍 Function: eask-package-el-files () Return a list of package files with `.el` extension. ## 🔍 Function: eask-package-elc-files () Return a list of package files with `.elc` extension. ## 🔍 Function: eask-package-multi-p () Return `nil` if single file package. ## 🔍 Function: eask-package-single-p () Return `t` if single file package. ## 🔍 Function: eask-unpacked-size () Return size of the current package. {{< hint warning >}} ⚠️ This returns a string not bytes. {{< /hint >}} # 🚩 Progress ## 🔍 Macro: eask-with-progress (`msg-start` `body` `msg-end`) Create execution with the responsive message output. ```elisp (eask-with-progress "Downloading files... " (eask-with-verbosity 'debug ; Often used with `eask-with-verbosity' ;; Execute some operations.. ) "done ✓") ``` Expect output: ```text Downloading files... done ✓ ``` ## 🔍 Function: eask-print-log-buffer (&optional `buffer-or-name`) Print buffer and highlight the `errors` and `warnings`. ```elisp (eask-print-log-buffer "*Package-Lint*") ``` {{< hint info >}} 💡 This is handy for linters that create a buffer to display **errors** and **warnings**. {{< /hint >}} # 🚩 Help ## 🔍 Function: eask-help (`command` &optional `print-or-exit-code`) Print help manual located under `lisp/help/` directory. ```elisp (eask-help "core/search") ; Exit code 1 ``` Alternatively, you can specify the exit code using the second parameter, `print-or-exit-code`, which defaults to 1. ```elisp (eask-help "core/search" 4) ; Exit code 4 ``` To prevent the program from exiting with a code, pass any non-nil value. ```elisp (eask-help "core/search" t) ; Skip exiting ``` {{< hint info >}} 💡 This is used when a command fails, and would like to give users some tips! {{< /hint >}} # 🚩 Utilities ## 🔍 Function: eask-guess-package-name () Return the possible package name. ## 🔍 Function: eask-guess-entry-point () Return the possible package's entry point. ================================================ FILE: docs/content/Development-API/_index.zh-tw.md ================================================ --- title: 開發 API weight: 700 --- 本文檔提供了對公共 Eask API 的引用,您可以在您的項目和 Eask 的擴展。 {{< toc >}} # 🚩 入口點 ## 🔍 代碼段: _prepare.el 加載 `lisp/_prepare.el` 以開始使用其他 Eask API。 ```elisp (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ``` 每個 Elisp 腳本都應該在文件的最頂部有這個片段。 ## 🔍 巨集: eask-start (&rest `body`) 命令入口點。 每個命令文件都應在文件的某處包含此宏。 ```elisp (eask-start ;; TODO: 在這裡設計你的命令! ) ``` # 🚩 環境 ## 🔍 變數: eask-has-colors 如果終端支援顏色,則傳回非零。 ```elisp (when eask-has-colors ... ``` ## 🔍 變數: eask-homedir Eask 的主目錄路徑。 ```elisp (message "%s" eask-homedir) ``` ## 🔍 Variable: eask-userdir Eask 的使用者目錄路徑。 ```elisp (message "%s" eask-userdir) ; ~/ ``` ## 🔍 Variable: eask-package-sys-dir Eask 全局 elpa 目錄;它將被視為系統全域的套件。 ```elisp (message "%s" eask-package-sys-dir) ; ~/.eask/30.2/elpa/ ``` ## 🔍 變數: eask-invocation Eask的呼叫程式路徑。 ```elisp (message "%s" eask-invocation) ``` 它可以是 `node` 可執行檔或 `eask` 執行檔本身。 ## 🔍 變數: eask-is-pkg 如果 Eask 已打包,則傳回非零。 ```elisp (when eask-is-pkg ... ``` ## 🔍 變數: eask-rest 命令分隔符號 `--` 之後的 Eask 參數;傳回一個列表。 ```sh eask -- args0 args1 ``` 輸出: ```elisp (message "%s" eask-rest) ; '(args0 args1) ``` ## 🔍 函式: eask-rest () 命令分隔符號 `--` 之後的 Eask 參數;傳回一個字串。 ```sh eask -- args0 args1 ``` 輸出: ```elisp (message "%s" (eask-rest)) ; "args0 args1" ``` # 🚩 核心 ## 🔍 變數: eask-lisp-root 從項目根目錄指向 `lisp` 目錄。 ```elisp (message "%s" eask-lisp-root) ; path/to/eask/cli/lisp/ ``` ## 🔍 函式: eask-working-directory () 傳回將要執行的程式的工作目錄。 ```elisp (message "%s" (eask-working-directory)) ; path/to/current/work/space/ ``` ## 🔍 函式: eask-command () 返回字符串中的當前命令。假設命令是: ```sh eask init ``` 然後, ```elisp (message "%s" (eask-command)) ; init ``` ## 🔍 函式: eask-command-check (`version`) 如果目前的指令需要最低的 `version` 就會報錯。 ```elisp (eask-command-check "27.1") ; 此指令需要 27.1 及以上版本! ``` ## 🔍 函式: eask-command-p (`commands`) 如果 COMMANDS 是目前命令,則傳回 `t`。 ## 🔍 函式: eask-special-p () 如果在沒有 Eask 文件存在的情況下可以運行的命令,則返回 `t`。 這允許一些命令仍然可以在不定義用戶的情況下執行目錄。 當您想在沒有的情況下進行正常操作時,這會很方便 觸摸用戶目錄。 ## 🔍 函式: eask-execution-p () 如果命令是執行命令,則傳回 `t`。 加入這項功能是因為我們不想污染 `error` 和 `warn` 函數。 ## 🔍 函式: eask-checker-p () 如果運行 Eask 作為檢查器,則返回 `t`。 如果沒有這個標誌,一旦發生錯誤,進程就會終止。此標誌允許您在不報告錯誤的情況下運行所有操作。 ## 🔍 函式: eask-script (`script`) 返回完整的腳本文件名。 ```elisp (eask-script "extern/pacakge") ; {project-root}/lisp/extern/package.el ``` ## 🔍 函式: eask-load (`script`) 加載另一個 eask 腳本。 ```elisp (eask-load "extern/ansi") ; load {project-root}/lisp/extern/ansi.el file ``` ## 🔍 函式: eask-call (`script`) 調用另一個 eask 腳本。 ```elisp (eask-call "clean/elc") ; call command `eask clean-elc` ``` {{< hint info >}} 💡 我們不經常呼叫它,因為我們不希望直接執行另一個命令! {{< /hint >}} ## 🔍 函式: eask-import (`url`) 從 url 載入並評估腳本。 ```elisp (eask-import "https://raw.githubusercontent.com/emacsmirror/emacswiki.org/master/yes-no.el") ;; 該腳本將在導入後即可使用! ``` ## 🔍 巨集: eask-defvc< (`version` &rest `body`) 如果 Emacs 版本低於特定版本,則定義範圍。 `VERSION` 是一個整數,將與 `emacs-major-version` 進行比較。 ```elisp (eask-defvc< 28 ;; 這在 Emacs 28 之前是缺失的; 定義它! (defvar package-native-compile nil)) ``` {{< hint info >}} 💡 這用於 Emacs 兼容性! {{< /hint >}} ## 🔍 巨集: eask--silent (&rest `body`) 將來自範圍內標準輸出的所有消息靜音。 ```elisp (eask--unsilent (message "你聽不到我! :(")) ``` ## 🔍 巨集: eask--unsilent (&rest `body`) 取消靜音來自範圍內標準輸出的所有消息。 ```elisp (eask--unsilent (message "你聽的到我! :)")) ``` ## 🔍 函式: eask-dependencies () 返回依賴項列表。 元素應該是 `(NAME . VERSION)` 或 `(NAME . RECIPE-FORMAT)`。 ## 🔍 函式: eask-pkg-init (&optional `force`) 初始化包以供使用。 ```elisp (eask-start (eask-pkg-init) ;; 現在您可以使用安裝在 `package-user-dir` 中的包 ) ``` {{< hint info >}} 💡 這通常在 **eask-start** 之後調用! {{< /hint >}} ## 🔍 巨集: eask-with-archives (`archives` &rest `body`) 臨時使存檔可用的範圍。 參數 `archives` 可以是字串或字串列表。 ```elisp (eask-with-archives "melpa" (eask-package-install 'dash)) ; 安裝僅在 MELPA 中定義的包 ``` {{< hint info >}} 💡 當您需要某些存檔中的特定套件時,這非常方便。 {{< /hint >}} ## 🔍 函式: eask-archive-install-packages (`archives` &rest `names`) 使用 archives 設定安裝套件。 參數 `names` 可以是符號或符號列表。 ```elisp (eask-archive-install-packages '("gnu" "melpa") 'el2org) ; 接受多個參數. ``` {{< hint info >}} 💡 這只會在套件遺失時安裝套件。 {{< /hint >}} ## 🔍 函式: eask-package-desc (`name` &optional `current`) 為包構建包描述符。 `CURRENT` 表示已安裝的包; 否則它將返回任何可用的來自選定包檔案的包。 ## 🔍 函式: eask-argv (`index`) 通过索引返回一个命令行参数。 ## 🔍 函式: eask-args () 返回從命令行參數中提取的列表。 ```sh eask info --verbose 4 foo bar ``` 它會忽略 `--verbose` 和 `4`,只返回 `foo` 和 `bar`。 ## 🔍 變數: eask-file 當前加載的 Eask 文件的路徑。 ## 🔍 變數: eask-file-root 當前加載的 Eask 文件的目錄。 ## 🔍 函式: eask--match-file (`name`) 檢查 NAME 是否是我們的目標 Eask 文件,然後返回它。 以下輸出來自 Emacs 28.1: ```elisp (eask--match-file "Eask") ; t (eask--match-file "Eask.28") ; t (eask--match-file "Eask.28.1") ; t (eask--match-file "Eask.29") ; nil (eask--match-file "Easkfile") ; t (eask--match-file "Easkfile.28") ; t (eask--match-file "Easkfile.29") ; nil ``` ## 🔍 函式: eask--all-files (&optional `dir`) 從 DIR 返回 Eask 文件列表。 考慮以下目錄樹: ```text . root ├── Eask ├── Eask.28 └── Eask.29 ``` 以下輸出來自 Emacs 28.1: ```elisp (eask--all-files "/root/") ; '(Eask Eask.28) ``` ## 🔍 函式: eask--find-files (`start-path`) 從 START-PATH 找到 Eask 文件。 考慮以下目錄樹: ```text .project ├─ src │ └── config.el ├── Eask ├── Eask.28 └── Eask.29 ``` 以下輸出來自 Emacs 28.1: ```elisp (eask--find-files "/project/src/config.el") ; '(/project/Eask /project/Eask.28) ``` ## 🔍 函式: eask-file-try-load (`start-path`) 嘗試在 START-PATH 中加載 Eask 文件。 ```elisp (eask--find-files "/project/src/") ; t ``` ## 🔍 函式: eask-network-insecure-p () 如果當前 Emacs 會話允許不安全的網絡連接,則返回 `t`。 # 🚩 旗標 ## 🔍 函式: eask-global-p () 如果啟用了 `global` 選項,則返回 `t`。 ```elisp (when (eask-global-p) user-emacs-directory) ; ~/.eask/ ``` ## 🔍 函式: eask-config-p () 如果啟用了 `config` 選項,則返回 `t`。 ```elisp (when (eask-config-p) user-emacs-directory) ; ~/.emacs.d ``` {{< hint info >}} 💡 如果選項 `--config` 和 `--global` 都打開,則選擇全局空間。 {{< /hint >}} ## 🔍 函式: eask-local-p () 這使用當前工作區,這是默認設置。 ```elisp (when (eask-local-p) user-emacs-directory) ; ./.eask/{emacs-version}/ ``` {{< hint info >}} 💡 此函數僅在 `(eask-global-p)` 和 `(eask-config-p)` 是 false 時返回 `t`! {{< /hint >}} ## 🔍 函式: eask-all-p () 如果啟用了 `all` 選項,則返回 `t`。 ```elisp (when (eask-all-p) ;; 運行所有測試 ...) ``` ## 🔍 函式: eask-quick-p () 如果啟用了 `quick` 選項,則返回 `t`。 ```elisp (unless (eask-quick-p) (load user-init-file) ...) ``` ## 🔍 函式: eask-force-p () 如果啟用了 `force` 選項,則返回 `t`。 ```elisp (package-delete .. (eask-force-p)) ``` ## 🔍 函式: eask-dev-p () 如果啟用了 `development` 選項,則返回 `t`。 ```elisp (when (eask-dev-p) (package-install 'ert-runner)) ; 安裝開發依賴 ``` ## 🔍 函式: eask-debug-p () 如果啟用了 `debug` 選項,則返回 `t`。 ```elisp (when (eask-debug-p) (error "在調試模式下執行...")) ``` ## 🔍 函式: eask-strict-p () 如果啟用了 `strict` 選項,則返回 `t`。 ```elisp (setq byte-compile-error-on-warn (eask-strict-p)) ``` ## 🔍 函式: eask-timestamps-p () 如果啟用/禁用 `timestamps` 選項,則返回 `t` / `nil`。 這些標誌不能在同一命令中共存。 ```elisp (when (eask-timestamps-p) (message "打印帶有時間戳的日誌!")) ``` ## 🔍 函式: eask-log-level-p () 如果啟用/禁用 `log-level` 選項,則返回 `t` / `nil`。 這些標誌不能在同一命令中共存。 ```elisp (when (eask-log-level-p) (message "打印帶有級別前綴的日誌!")) ``` ## 🔍 函式: eask-log-file-p () 如果啟用/禁用 `log-file` 選項,則返回 `t` / `nil`。 這些標誌不能在同一命令中共存。 ```elisp (when (eask-log-file-p) (message "讓我們創建一個日誌文件!")) ``` ## 🔍 函式: eask-no-color-p () 如果啟用了 `color` 選項,則返回 `t`。 ```elisp (unless (eask-no-color-p) (message "此字符串沒有 ANSI 代碼!")) ``` ## 🔍 函式: eask-allow-error-p () 如果啟用了 `allow-error` 選項,則返回 `t`。 ```elisp (unless (eask-allow-error-p) (error "停在這裡。")) ``` ## 🔍 函式: eask-insecure-p () 如果啟用了 `insecure` 選項,則返回 `t`。 ```elisp (when (eask-insecure-p) ;; 做一些危險的工作? ) ``` ## 🔍 函式: eask-proxy () ## 🔍 函式: eask-http-proxy () ## 🔍 函式: eask-https-proxy () ## 🔍 函式: eask-no-proxy () 返回一個 **string** 表示 `hostname` + `port number`。 ```sh eask [command] --proxy "localhost:1000" eask [command] --http-proxy "localhost:2000" eask [command] --https-proxy "localhost:3000" eask [command] --no-proxy "localhost:4000" ``` ## 🔍 函式: eask-destination () 返回一個 **string** 表示目的地(輸出路徑)。 ```elisp (write-file (or (eask-destination) "./dist")) ; 將文件寫入目標 ``` ## 🔍 函式: eask-depth () 返回一個**整數**表示當前打印層級的深度。 ```elisp (setq print-level (eask-depth)) ``` ## 🔍 函式: eask-verbose () 返回一個 **整數** 表示冗長級別。 ```elisp (when (= (eask-verbose) 4) (setq byte-compile-verbose t)) ``` # 🚩 `Eask` 文件 這些函數是 `Eask`-file DSL 的實際實現; 和將單詞 `eask-` 作為函數前綴。 有關詳細信息,請參閱 [DSL](https://emacs-eask.github.io/DSL/) 部分。 ## 🔍 變數: eask-package 它在 plist 中保存包的 `NAME`、`VERSION` 和 `DESCRIPTION`。 ```elisp (plist-get eask-package :name) ; 返回包名 ``` 從該變量擴展的三個函數: - `(eask-package-name)` - `(eask-package-version)` - `(eask-package-description)` ## 🔍 變數: eask-package-file 指向打包主文件。 ## 🔍 變數: eask-package-desc 來自包主文件的包描述符。 ```elisp (package-desc-p eask-package-desc) ; 返回 t ``` {{< hint warning >}} ⚠ 如果不能正確構造包描述符,這可以是 **nil**! {{< /hint >}} ## 🔍 變數: eask-files 持有通配符規範中的文件模式列表。 ## 🔍 變數: eask-scripts 包含可用腳本的列表,用戶可以使用 `eask run-script` 命令。 ## 🔍 變數: eask-depends-on-emacs 保存有關 Emacs 最低版本的信息。 ```elisp (depends-on "emacs" "26.1") ``` 函數將返回字符串中的 Emacs 版本。 - `(eask-depends-emacs-version)` - 返回 `"26.1"` ## 🔍 變數: eask-depends-on 持有依賴項列表。 ## 🔍 變數: eask-depends-on-dev 持有開發使用的依賴項列表。 ## 🔍 函式: eask-f-package (`name` `version` `description`) 別名 `package`. ## 🔍 函式: eask-f-website-url (`url`) 別名 `website-url`. ## 🔍 函式: eask-f-keywords (&rest `keywords`) 別名 `keywords`. ## 🔍 函式: eask-f-author (`name` &optional `email`) 別名 `author`. ## 🔍 函式: eask-f-license (`name`) 別名 `license`. ## 🔍 函式: eask-f-package-file (`file`) 別名 `package-file`. ## 🔍 函式: eask-f-files (`pkg` &rest `args`) 別名 `files`. ## 🔍 函式: eask-f-script (`name` `command` &rest `args`) 別名 `script`. ## 🔍 函式: eask-f-source (`name` &optional `location`) 別名 `source`. ## 🔍 函式: eask-f-source-priority (`name` &optional `priority`) 別名 `source-priority`. ## 🔍 函式: eask-f-depends-on (`pkg` &rest `args`) 別名 `depends-on`. ## 🔍 函式: eask-f-development (&rest `dependencies`) 別名 `development`. ## 🔍 函式: eask-f-exec-paths (&rest `dirs`) 別名 `exec-paths`. ## 🔍 函式: eask-f-load-paths (&rest `dirs`) 別名 `load-paths`. # 🚩 信息紀錄 具有時間戳和日誌級別的記錄器實用程序。 日誌級別值在函數 `eask--verb2lvl` 中定義。 | 等級 | 描述 | 值 | |:--------|:---------------------------------------------------|:---| | `debug` | 指定對調試應用程序最有用的細粒度信息事件。 | 4 | | `log` | 指定普通消息。 | 3 | | `info` | 指定在粗粒度級別突出顯示應用程序進度的信息性消息。 | 2 | | `warn` | 指定潛在的有害情況。 | 1 | | `error` | 指定可能仍允許應用程序繼續運行的錯誤事件。 | 0 | The default level is `log`. ## 🔍 變數: eask-verbosity 詳細級別表示為整數。 ```elisp (setq eask-verbosity 4) ; 你可以設置從 0 到 4 ``` ## 🔍 變數: eask-timestamps 記錄帶有時間戳的消息。 ```elisp (setq eask-timestamps t) ``` Output: ```text 2022-04-14 13:44:46 這是一條帶有時間戳的消息 ``` ## 🔍 變數: eask-log-level 記錄消息級別。 (默認值:`nil`) ```elisp (setq eask-log-level t) ``` 輸出: ```text [DEBUG] 這是一條具有日誌級別的 DEBUG 消息 ``` ## 🔍 變數: eask-log-file 天氣生成日誌文件。 (默認值:`nil`) ```elisp (setq eask-log-level t) ``` 使用命令 `cat` 查看日誌, ```text cat /.log/messages.log ``` ## 🔍 變數: eask-level-color 定義每個日誌級別顏色。 ```elisp (setq eask-level-color '((debug . ansi-blue) (log . ansi-white) (info . ansi-cyan) (warn . ansi-yellow) (error . ansi-red))) ``` ## 🔍 函式: eask-reach-verbosity-p (`symbol`) 達到詳細等級時執行。 ```elisp (when (eask-reach-verbosity-p 'debug) ;; TODO: 在這裡執行.. ) ``` ## 🔍 巨集: eask-with-verbosity (`symbol` &rest `body`) 定義消息範圍。 ```elisp (eask-with-verbosity 'debug ;; TODO: 在這裡執行.. ) ``` 除非冗長,否則此宏範圍內的所有內容都將被靜音。 僅當您指定 `--verbose 4` 時才會打印 全局選項。 ## 🔍 巨集: eask-with-verbosity-override (`symbol` &rest `body`) 定義覆蓋消息範圍。 ```elisp (eask-with-verbosity 'debug (eask-with-verbosity-override 'log ;; TODO: 在這裡執行.. ) (eask-with-verbosity-override 'info ;; TODO: 在這裡執行.. )) ``` 就像宏 `eask-with-verbosity` 一樣;但如果無法顯示則強制顯示消息。 ## 🔍 函式: eask-debug (`msg` &rest `args`) ```elisp (eask-debug "這是調試信息") ``` ```text 2022-04-14 17:31:54 [DEBUG] 這是調試信息 ``` ## 🔍 函式: eask-log (`msg` &rest `args`) ```elisp (eask-log "這是日誌消息") ``` ```text 2022-04-14 17:31:54 [LOG] 這是日誌消息 ``` ## 🔍 函式: eask-info (`msg` &rest `args`) ```elisp (eask-info "這是信息消息") ``` ```text 2022-04-14 17:31:54 [INFO] 這是信息消息 ``` ## 🔍 函式: eask-warn (`msg` &rest `args`) ```elisp (eask-warn "這是警告消息") ``` ```text 2022-04-14 17:31:54 [WARNING] 這是警告消息 ``` ## 🔍 函式: eask-error (`msg` &rest `args`) ```elisp (eask-error "這是錯誤信息") ``` ```text 2022-04-14 17:31:54 [ERROR] 這是錯誤信息 ``` ## 🔍 函式: eask-print (`msg` &rest `args`) 標準輸出列印不含換行符。 ```elisp (eask-println "打印到標準輸出!") ``` ## 🔍 函式: eask-println (`msg` &rest `args`) 與函數 `esk-print` 類似,但末尾包含換行符。 ```elisp (eask-println "打印到標準輸出! (有換行符)") ``` ## 🔍 函式: eask-msg (`msg` &rest `args`) 類似於 `message` 函數,但會用顏色替換 unicode。 ```elisp (eask-msg "用換行符打印此消息!") ``` ## 🔍 函式: eask-write (`msg` &rest `args`) 類似於 eask-msg 函數,但末尾沒有換行符。 ```elisp (eask-write "不帶換行符打印此消息...") ``` ## 🔍 函式: eask-report (&rest `args`) 報告錯誤/警告取決於嚴格標誌。 ```elisp (eask-report "This can be warning or error") ``` 見選項 [--strict](https://emacs-eask.github.io/Getting-Started/Commands-and-options/#---strict). # 🚩 退出代碼 ## 🔍 變數: eask--exit-code 退出代碼規格。 ## 🔍 函式: eask-exit-code (&optional `key`) 以變數 `eask--exit-code` 中的關鍵符號回傳 exit code。 ```elisp (eask-exit-code 'misuse) ; 按符號 ``` ## 🔍 函式: eask--exit (&optional `exit-code` &rest `_`) 傳送退出代碼。 這會結束 Emacs 程序。 ```elisp (eask--exit 2) ; 按數字 (eask--exit 'misuse) ; 按符號 ``` # 🚩 錯誤處理 ## 🔍 變數: eask--ignore-error-p 非 `nil` 是為了防止 Emacs 被殺死。 ```elisp (let ((eask--ignore-error-p t)) (error "Emacs can't die! :P")) ``` ## 🔍 變數: eask-inhibit-error-message 非 `nil` 停止錯誤/警告消息。 ```elisp (let ((eask-inhibit-error-message t)) (error "This won't display at all!")) ``` ## 🔍 巨集: eask-ignore-errors (&rest `body`) 防止 Emacs 被殺死。 ```elisp (eask-ignore-errors (error "Emacs can't die! :P")) ``` ## 🔍 巨集: eask--silent-error (&rest `body`) 禁止顯示錯誤/警告消息。 ```elisp (eask--silent-error (error "This won't display at all!")) ``` ## 🔍 巨集: eask-ignore-errors-silent (&rest `body`) 防止 Emacs 被殺死並禁止顯示錯誤/警告消息。 ```elisp (eask-ignore-errors-silent (error "Nothing happens!")) ``` # 🚩 文件 ## 🔍 函式: eask-package-files () 返回包文件列表。 ## 🔍 函式: eask-package-el-files () 返回擴展名為 `.el` 的包文件列表。 ## 🔍 函式: eask-package-elc-files () 返回擴展名為 `.elc` 的包文件列表。 ## 🔍 函式: eask-package-multi-p () 如果是單個文件包,則返回 `nil`。 ## 🔍 函式: eask-package-single-p () 如果是單個文件包,則返回 `t`。 ## 🔍 函式: eask-unpacked-size () 返回當前包的大小。 {{< hint warning >}} ⚠️ 這將返回一個字符串而不是字節。 {{< /hint >}} # 🚩 進度 ## 🔍 巨集: eask-with-progress (`msg-start` `body` `msg-end`) 使用響應消息輸出創建執行。 ```elisp (eask-with-progress "檔案下載中s... " (eask-with-verbosity 'debug ; 通常與 `eask-with-verbosity` 一起使用 ;; 執行一些操作.. ) "完成 ✓") ``` 期望輸出: ```text 檔案下載中... 完成 ✓ ``` ## 🔍 函式: eask-print-log-buffer (&optional `buffer-or-name`) 打印緩衝區並突出顯示 `錯誤` 和 `警告`。 ```elisp (eask-print-log-buffer "*Package-Lint*") ``` {{< hint info >}} 💡 這對於創建 buffer 來顯示 **errors** 和 **warnings** 的 linters 會很方便。 {{< /hint >}} # 🚩 幫助 ## 🔍 函式: eask-help (`command`) 打印位於 `lisp/help/` 目錄下的幫助手冊。 ```elisp (eask-help "core/search") ; 程式的退出碼 1 ``` 另外,您也可以使用第二個參數 `print-or-exit-code` 指定退出代碼,預設值為 1。 ```elisp (eask-help "core/search" 4) ; 程式的退出碼 4 ``` 若要防止程式以代碼退出,請傳入任何非零值。 ```elisp (eask-help "core/search" t) ; 跳過退出 ``` {{< hint info >}} 💡 這是在命令失敗時使用的,想給用戶一些提示! {{< /hint >}} # 🚩 實用工具 ## 🔍 函式: eask-guess-package-name () 返回可能的包名稱。 ## 🔍 函式: eask-guess-entry-point () 返回可能的包的入口點。 ================================================ FILE: docs/content/Examples/Emacs-Configuration/_index.en.md ================================================ --- title: ⚙️ Emacs Configuration weight: 100 --- `Eask` is the magic file that `eask` will read it as the init file in Emacs. The syntaxes are similar to the `Cask` file, but different. ```elisp ;; -*- mode: eask; lexical-binding: t -*- (package "Emacs configuration's name" "0.1.0" "Your Emacs configuration's description") ; optional (website-url "https://github.com/owner/repo") (keywords "config") (package-file "init.el") ; optional (script "test" "echo \"Error: no test specified\" && exit 1") (files "early-init.el" "init.el" "lisp/*.el" "site-lisp/*.el") (source "gnu") (source "melpa") (depends-on "emacs" "26.1") (depends-on "auto-complete") (depends-on "dash") (depends-on "f") (depends-on "flycheck") (depends-on "helm") (depends-on "magit") (depends-on "popup") (depends-on "projectile") (depends-on "s") (depends-on "smartparens") (depends-on "yasnippet") ``` {{< hint info >}} 💡 You would need to use **-c** or **--config** option to manage your configuration's packages! {{< /hint >}} ================================================ FILE: docs/content/Examples/Emacs-Configuration/_index.zh-tw.md ================================================ --- title: ⚙️ Emacs 配置 weight: 100 --- `Eask` 是魔法文件,`eask` 會將其讀取為 Emacs 中的初始化文件。語法類似於 `Cask` 文件,但有所不同。 ```elisp ;; -*- mode: eask; lexical-binding: t -*- (package "Emacs configuration's name" "0.1.0" "Your Emacs configuration's description") ; optional (website-url "https://github.com/owner/repo") (keywords "config") (package-file "init.el") ; optional (script "test" "echo \"Error: no test specified\" && exit 1") (files "early-init.el" "init.el" "lisp/*.el" "site-lisp/*.el") (source "gnu") (source "melpa") (depends-on "emacs" "26.1") (depends-on "auto-complete") (depends-on "dash") (depends-on "f") (depends-on "flycheck") (depends-on "helm") (depends-on "magit") (depends-on "popup") (depends-on "projectile") (depends-on "s") (depends-on "smartparens") (depends-on "yasnippet") ``` {{< hint info >}} 💡 您需要使用 **-c** 或 **--config** 選項來管理您的配置包! {{< /hint >}} ================================================ FILE: docs/content/Examples/Package-Development/_index.en.md ================================================ --- title: 📦 Package Development weight: 200 --- `Eask` is the magic file that `eask` will read it as the init file in Emacs. The syntaxes are similar to the `Cask` file, but different. ```elisp ;; -*- mode: eask; lexical-binding: t -*- (package "your-package" "0.1.0" "Your package description") (website-url "https://github.com/owner/repo") (keywords "example" "tool") (package-file "your-package-file.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") (depends-on "dash") (depends-on "f") (depends-on "s") ``` ================================================ FILE: docs/content/Examples/Package-Development/_index.zh-tw.md ================================================ --- title: 📦 Package 開發 weight: 200 --- `Eask` 是魔法文件,`eask` 會將其讀取為 Emacs 中的初始化文件。語法類似於 `Cask` 文件,但有所不同。 ```elisp ;; -*- mode: eask; lexical-binding: t -*- (package "your-package" "0.1.0" "Your package description") (website-url "https://github.com/owner/repo") (keywords "example" "tool") (package-file "your-package-file.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") (depends-on "dash") (depends-on "f") (depends-on "s") ``` ================================================ FILE: docs/content/Examples/Real-project-examples/_index.en.md ================================================ --- title: ⚖️ Real project examples weight: 400 --- These are some projects and configurations using Eask: {{< toc >}} ## 🧾 Configuration - [JCS-Emacs](https://github.com/jcs-emacs/jcs-emacs) ## 📦 Packages - [lsp-mode](https://github.com/emacs-lsp/lsp-mode) - [evil](https://github.com/emacs-evil/evil) - [flycheck](https://github.com/flycheck/flycheck) - [auto-complete](https://github.com/auto-complete/auto-complete) - [dashboard](https://github.com/emacs-dashboard/dashboard) - [docker.el](https://github.com/Silex/docker.el) ## 📂 ELPA - [JCS ELPA](https://github.com/jcs-emacs/jcs-elpa) - [EINE ELPA](https://github.com/emacs-eine/elpa) ## 🧩 Others - [simple-httpd-cli](https://github.com/emacs-eine/simple-httpd-cli) - [google-translate-cli](https://github.com/emacs-eine/google-translate-cli) - [cat](https://github.com/emacs-eine/cat) ================================================ FILE: docs/content/Examples/Real-project-examples/_index.zh-tw.md ================================================ --- title: ⚖️ 真實項目實例 weight: 400 --- 這些是使用 Eask 的一些項目和配置: {{< toc >}} ## 🧾 配置 - [JCS-Emacs](https://github.com/jcs-emacs/jcs-emacs) ## 📦 包 - [lsp-mode](https://github.com/emacs-lsp/lsp-mode) - [evil](https://github.com/emacs-evil/evil) - [flycheck](https://github.com/flycheck/flycheck) - [auto-complete](https://github.com/auto-complete/auto-complete) - [dashboard](https://github.com/emacs-dashboard/dashboard) - [docker.el](https://github.com/Silex/docker.el) ## 📂 ELPA - [JCS ELPA](https://github.com/jcs-emacs/jcs-elpa) - [EINE ELPA](https://github.com/emacs-eine/elpa) ## 🧩 其他 - [simple-httpd-cli](https://github.com/emacs-eine/simple-httpd-cli) - [google-translate-cli](https://github.com/emacs-eine/google-translate-cli) - [cat](https://github.com/emacs-eine/cat) ================================================ FILE: docs/content/Examples/_index.en.md ================================================ --- title: Examples weight: 300 --- ================================================ FILE: docs/content/Examples/_index.zh-tw.md ================================================ --- title: 範例 weight: 300 --- ================================================ FILE: docs/content/FAQ/_index.en.md ================================================ --- title: FAQ weight: 900 --- Here is a list of general frequently asked questions. {{< toc >}} # 🔍 About Eask ## ❓ Do you need Node.JS to use Eask? The answer is **NO**. Eask builds native executable on every release, see our [release page](https://github.com/emacs-eask/cli/releases) to download it! However, [Node.JS][] is required if you are going to develop Eask! ## ❓ Who should use this tool? Here are our suggestions; if you plan to work on an OS-specific package (never going to other platforms), go for other tools. On the other hand, Eask aims to provide the best consistency between each OS. Alternatively, if you want to learn a tool that works everywhere, Eask is one of the best choices. ## ❓ Where can I download the Eask snapshot? You can download the latest executable (snapshot) in our [emacs-eask/binaries](https://github.com/emacs-eask/binaries) repository! # 🔍 Technology Choice ## ❓ Why Node.JS? [Node][Node.js] provides better support for various terminal applications compared to shell scripts. It offers features like a rich, colorful interface and access to the vast npm ecosystem, making cross-platform development more convenient. This is especially true since Microsoft acquired [npm][] Inc., likely ensuring strong support for their own systems. [Cask][] seems to have dropped support for Windows (while still supporting [WSL][]) after version `0.8.6`. Earlier versions were built on [Python][], but [Python][]’s support on Windows has traditionally been less reliable than [Node.js][]. See [issue #140](https://github.com/emacs-eask/cli/issues/140) for more information! ## ❓ Why JavaScript? There are many languages I could use to build around Eask, so why did I choose [JavaScript][]? I have three reasons: - [JavaScript][] is easy to learn. - It offers excellent cross-platform compatibility, thanks to the [Node.js][] runtime. - I just happen to know [JavaScript][], and I’m comfortable with it. I also considered [Rust][] and [Common Lisp][]. However, [Rust][] was still relatively new when I started this project, and [Common Lisp][], while powerful, has a steeper learning curve and is often seen as somewhat outdated. So, I went with [JavaScript][]. ## ❓ Why yargs? [yargs][] has a large and active community and is widely used in various tools. It’s fully cross-platform and, most importantly, works seamlessly on Linux, macOS, and Windows. One key difference between Eask and other alternatives is how they handle scripting. Tools like [Cask][], [makem.sh][], and [Eldev][] rely heavily on batch and bash scripts. Instead, we took a different approach by leveraging a high-level programming language-[JavaScript][]. This made development significantly easier, as we no longer need to worry about shell compatibility. The main drawback is the [Node.js][] runtime requirement, but we can mitigate this by packaging the entire CLI program into an executable. This way, users won’t need to install [Node][Node.js] or [npm][] beforehand to use Eask! # 🔍 Usage ## ❓ How to configure Eask? `Eask`-file is an Elisp file, similar to `.emacs` or `init.el`. Just as Emacs allows you to customize aspects you don’t like, Eask follows the same principle, letting you configure anything you dislike about Eask. Another way to configure your workspace is similar to configuring Emacs itself: - Use `.eask/VERSION_NO/early-init.el` (only after Emacs `27.1` onward) - Use `.eask/VERSION_NO/init.el` ## ❓ How can I install packages directly from the repository? There are several ways to do this, but the standard method is to define a [recipe format][] in the Eask file. ```elisp (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) ``` Eask builds the package once and hosts a local ELPA, allowing you to use it for later installation. This is the safest way to install packages as it simulates the most practical scenario. However, any other alternative would work as well since Eask is also an Elisp file. - [package-vc-install][] - [quelpa][] - [use-package][] - [straight.el][] # 🔍 Troubleshooting ## ❓ Why am I getting the error package target `tar`/`el` not found while installing? The example error message, ```text http://melpa.org/packages/lsp-mode-20220429.647.tar: Not found ``` The issue is caused by the mismatch from the backup archives. Generally, Eask will pick up the latest `archive-contents` from sources unless you have been pinging sources too many times. Then the source could block your IP for few minutes. You can either wait for few minutes for the source to remove you from their black list. Or wait for the backup archives to update to the latest version. The backup archives repository is [here](https://github.com/emacs-eask/archives). ## ❓ Why am I getting the error package is not installable? The example error message, ```text Package not installable `helm'; make sure package archives are included ``` First, determine the origin of the package and the specific source that provides its information. In the example above, `helm` is listed under the `melpa` source. To properly include it, update your **Eask**-file as follows: ```elisp ... (source "melpa") ; <- add this line (depends-on "helm") ``` ## ❓ Why am I seeing the error: "Package `emacs-XX.X' is unavailable"? The example error message, ```text Loading package information... done v Installing 1 specified package... - [1/1] Installing markdown-mode (20250226.231)... Package `emacs-28.1' is unavailable Wrong type argument: package-desc, nil ``` This error occurs when Emacs attempts to install a package that requires a newer version of Emacs. In some cases, the requirement does not come directly from the package itself but from one of its dependencies. You can either refrain from using this package or upgrade Emacs to the required version. ## ❓ Why am I getting git errors with status 2? If you get this sample error message: ```text Loading package information... done ✓ - Installing s (20210616.619)... Failed (status 2): git --no-pager remote get-url upstream . ... ``` You may have `bug-reference-prog-mode` enabled. It is not yet compatible with Eask and should be disabled when running any of Eask’s commands. See [this issue](https://github.com/emacs-eask/cli/issues/39#issuecomment-1150770740) for more information. ## ❓ Why am I getting tar exited with status 2? If you get this sample error message: ```text Created your-package-0.1.0.tar containing: tar exited with status 2 Error: Process completed with exit code 1. ``` You might get this error while using the BSD tar. The workaround is to use GNU tar instead. ```elisp (setq package-build-tar-executable "/path/to/gnu/tar") ``` In Windows, BSD tar is used by default. If you have Git installed, you can use the tar executable from Git; it uses GNU tar. Add the following code snippet to your Eask-file: ```elisp ;; Use GNU tar in Windows (when (memq system-type '(cygwin windows-nt ms-dos)) (setq package-build-tar-executable "C:/Program Files/Git/usr/bin/tar.exe")) ``` [emacs-eask/archives]: https://github.com/emacs-eask/archives [Cask]: https://github.com/cask/cask [makem.sh]: https://github.com/alphapapa/makem.sh [Eldev]: https://github.com/doublep/eldev [Node.js]: https://nodejs.org/ [npm]: https://www.npmjs.com/ [yargs]: https://www.npmjs.com/package/yargs [WSL]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux [JavaScript]: https://simple.wikipedia.org/wiki/JavaScript [Python]: https://www.python.org/ [Rust]: https://www.rust-lang.org/ [Common Lisp]: https://lisp-lang.org/ [recipe format]: https://github.com/melpa/melpa?tab=readme-ov-file#recipe-format [package-vc-install]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Fetching-Package-Sources.html [quelpa]: https://github.com/quelpa/quelpa [use-package]: https://github.com/jwiegley/use-package [straight.el]: https://github.com/radian-software/straight.el ================================================ FILE: docs/content/FAQ/_index.zh-tw.md ================================================ --- title: 常見問題 weight: 900 --- 以下是一般常見問題列表。 {{< toc >}} # 🔍 關於 Eask ## ❓ 您需要 Node.JS 才能使用 Eask 嗎? 答案是 **不**. Eask 在每個版本上構建本機可執行文件,您可以從我們的[發布頁面](https://github.com/emacs-eask/cli/releases) 下載! 但是,如果您要開發 Eask,那就需要需要 [Node.JS][]! ## ❓ 誰應該使用這個工具? 這是我們的建議; 如果您打算使用特定於操作系統的軟件包(從不去其他平台),去尋找其他工具。 另一方面,Eask 旨在提供每個操作系統之間的最佳一致性。 或者,如果你想學習一個隨處可用的 工具,Eask 是最好的選擇之一。 ## ❓ 我在哪裡可以下載 Eask snapshot? 您可以在我們的網站下載最新的可執行文件 (snapshot) [emacs-eask/binaries](https://github.com/emacs-eask/binaries) 代碼庫! # 🔍 技術選擇 ## ❓ 為什麼選擇 Node.JS? [Node][Node.js] 對各種終端應用程序有更好的支持(相比 shell 腳本)! 比如豐富多彩的界面,整個 [npm][] 社區等等。 所以你可以更輕鬆地構建跨平台軟件! 尤其是在微軟之後已經購買了 NPM 公司,並且可能會很好地支持他 們自己的系統。 在 `0.8.6` 版本之後,[Cask][] 似乎不支持 Windows(但支援 [WSL][])。 在裡面早期版本,他們使用的是 [Python][] 但由於 [Python][] 在 Windows 上的支持只是不如 [Node.JS][]。 有關詳細信息,請參閱[問題 #140](https://github.com/emacs-eask/cli/issues/140)! ## ❓ 為什麼選擇 JavaScript? 我可以使用許多語言來建立 Eask,為什麼我選擇 [JavaScript][]? 我有三個理由 - [JavaScript][] 容易學習。 - 得益於 [Node.js][] runtime,它提供了極佳的跨平台兼容性。 - 我恰好了解 [JavaScript][],而且用起來得心應手。 我也考慮過 [Rust][] 和 [Common Lisp][]。然而,當我開始這個專案時,[Rust][] 仍是比較新的語言,而 [Common Lisp][] 雖然功能強大,但學習曲線較陡,而且經常被視為有點落伍。因此,我選擇了 JavaScript。 ## ❓ 為什麼選擇 yargs? [yargs][] 擁有非常廣泛的社區; 和它已經被用在很多工具中。 它是跨平台的! 最重要的是,這是在 Linux、macOS 和 Windows 上運行良好的工具之一。 與 Eask 和其他替代方案相比,也存在主要差異。[Cask][]、[makem.sh][] 或 [Eldev][] 更依賴於 `batch` 和 `bash`。 我們選擇了一個路線不同,想把繁重的任務交給高層編程語言,[JavaScript][]。 開髮變得更加容易,因為我們不再需要關心不同類型的 shell! 缺點是 [Node.JS][] 的 runtime,但我們可以簡單地打包整個 CLI 程序變成可執行文件! 這樣我們就不需要安裝 [Node][Node.js] 和 [npm][]! # 🔍 用法 ## ❓ 如何設定 Eask? `Eask`-file 是一個 Elisp 檔案,類似於 `.emacs` 或 `init.el`。 正如 Emacs 允許您自訂您不喜歡的方面一樣,Eask 也遵循同樣的原則,讓您配置任何您不喜歡 Eask 的地方。 另一種配置工作區的方法與配置 Emacs 本身類似: - 使用 .eask/VERSION_NO/early-init.el (僅限於 Emacs 27.1 以後) - 使用 .eask/VERSION_NO/init.el ## ❓ 如何直接從套件庫安裝套件? 有幾種方法可以做到這一點,但標準的方法是在 Eask 檔案中定義一個 [recipe format][]。 ```elisp (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) ``` Eask 會一次建立套件,並寄存本機 ELPA,讓您稍後安裝時使用。這是安裝套件最安全的方式,因為它模擬了最實際的情況。 不過,由於 Eask 也是一個 Elisp 檔案,因此任何其他替代方式也同樣適用。 - [package-vc-install][] - [quelpa][] - [use-package][] - [straight.el][] # 🔍 故障排除 ## ❓ 為什麼安裝時出現錯誤 package target `tar`/`el` not found? 示例錯誤消息, ```text http://melpa.org/packages/lsp-mode-20220429.647.tar: Not found ``` 該問題是由備份存檔不匹配引起的。 一般來說,Eask 將從源中獲取最新的 `archive-contents` 除非你已經 ping 源太多次。 然後來源可能會阻止您的 IP 一段時間分鐘。 您可以等待幾分鐘讓消息來源將您從他們的黑名單。 或者等待備份存檔更新到最新版本。 這 備份存檔存儲庫在 [此處](https://github.com/emacs-eask/archives)。 ## ❓ 為什麼我收到錯誤包不可安裝? 示例錯誤消息, ```text Package not installable `helm'; make sure package archives are included ``` 你需要先問問自己; 包裹從哪裡來,是什麼具體來源保存此包信息。 從上面的示例消息中, `helm` 列在 `melpa` 源代碼中。 您將不得不編輯您的 **Eask** 文件像這樣: ```elisp ... (source "melpa") ; <- add this line (depends-on "helm") ``` ## ❓ 為什麼我會看到錯誤?「Package emacs-XX.X' is unavailable」? 示例錯誤消息, ```text Loading package information... done v Installing 1 specified package... - [1/1] Installing markdown-mode (20250226.231)... Package `emacs-28.1' is unavailable Wrong type argument: package-desc, nil ``` 當 Emacs 嘗試安裝一個需要較新版本 Emacs 的套件時,就會發生這個錯誤。 在某些情況下,此需求並非直接來自套件本身,而是來自其依賴的其中一個套件。 您可以選擇不使用這個套件,或是將 Emacs 升級到所需的版本。 ## ❓ 為什麼我會收到狀態為 2 的 git 錯誤? 如果您收到此示例錯誤消息: ```text Loading package information... done ✓ - Installing s (20210616.619)... Failed (status 2): git --no-pager remote get-url upstream . ... ``` 您可能啟用了 `bug-reference-prog-mode` 。 它還不兼容 Eask, 運行 Eask 的任何命令時都應禁用。 參見[問題 #39](https://github.com/emacs-eask/cli/issues/39#issuecomment-1150770740) 了解更多信息。 ## ❓ 為什麼我會以狀態 2 退出 tar? 如果您收到此示例錯誤消息: ```text Created your-package-0.1.0.tar containing: tar exited with status 2 Error: Process completed with exit code 1. ``` 使用 BSD tar 時您可能會收到此錯誤。解決方法是使用 GNU tar 代替。 ```elisp (setq package-build-tar-executable "/path/to/gnu/tar") ``` 在 Windows 中,默認是使用 BSD tar。如果安裝了 Git,則可以使用 Git 中的 tar 可執行文件;它使用 GNU tar。 將以下代碼片段添加到您的 Eask 文件中: ```elisp ;; 在Windows中使用 GNU tar (when (memq system-type '(cygwin windows-nt ms-dos)) (setq package-build-tar-executable "C:/Program Files/Git/usr/bin/tar.exe")) ``` [emacs-eask/archives]: https://github.com/emacs-eask/archives [Cask]: https://github.com/cask/cask [makem.sh]: https://github.com/alphapapa/makem.sh [Eldev]: https://github.com/doublep/eldev [Node.js]: https://nodejs.org/ [npm]: https://www.npmjs.com/ [yargs]: https://www.npmjs.com/package/yargs [WSL]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux [JavaScript]: https://simple.wikipedia.org/wiki/JavaScript [Python]: https://www.python.org/ [Rust]: https://www.rust-lang.org/ [Common Lisp]: https://lisp-lang.org/ [recipe format]: https://github.com/melpa/melpa?tab=readme-ov-file#recipe-format [package-vc-install]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Fetching-Package-Sources.html [quelpa]: https://github.com/quelpa/quelpa [use-package]: https://github.com/jwiegley/use-package [straight.el]: https://github.com/radian-software/straight.el ================================================ FILE: docs/content/Getting-Started/Advanced-Usage/_index.en.md ================================================ --- title: 🔧 Advanced Usage weight: 400 --- {{< toc >}} `Eask` is just a regular Emacs Lisp file and should be read from Emacs itself! You can do: ```elisp ; Regular Eask file content... (setq byte-compile-error-on-warn t) ; Signal error if warning occurred ``` # 🪝 Hooks `eask` provides some hooks which enable you to execute code before and after each command. The hooks look like so: - `eask-before-COMMAND-hook` - `eask-after-COMMAND-hook` For example, to consider warnings as errors when byte-compiling with the command `eask compile`: ```elisp (add-hook 'eask-before-compile-hook (lambda () (setq byte-compile-error-on-warn t))) ``` This is also equivalent to option `--strict`: ```sh eask compile [FILES..] --strict ``` Or hooks that run on every command: - `eask-before-command-hook` - `eask-after-command-hook` ```elisp (add-hook 'eask-before-command-hook (lambda () (message "%s" (eask-command)))) ; print the current command ``` For subcommands that contain spaces, will concatenate with `/`: ```sh eask lint checkdoc # lint/checkdoc eask generate license # generate/license ``` therefore, ```elisp (add-hook 'eask-before-lint/checkdoc-hook (lambda () ;; do stuff before checkdoc linting... )) ``` # 📇 Adding your own command You can add your own command through our command interface: ```elisp (eask-defcommand my-test-command "A test command that prints out useless message." (message "This is a test command!")) ``` ================================================ FILE: docs/content/Getting-Started/Advanced-Usage/_index.zh-tw.md ================================================ --- title: 🔧 進階用法 weight: 400 --- {{< toc >}} `Eask` 只是一個普通的 Emacs Lisp 文件,應該從 Emacs 本身讀取! 你可以做: ```elisp ; 常規 Eask 文件內容... (setq byte-compile-error-on-warn t) ; 出現警告時信號錯誤 ``` # 🪝 Hooks `eask` 提供了一些 hooks,使您能夠在每個命令之前和之後執行代碼。 hook 看起來像這樣: - `eask-before-COMMAND-hook` - `eask-after-COMMAND-hook` 例如,在使用命令 `eask compile` 進行字節編譯時將警告視為錯誤: ```elisp (add-hook 'eask-before-compile-hook (lambda () (setq byte-compile-error-on-warn t))) ``` 這也等同於選項 `--strict`: ```sh eask compile [FILES..] --strict ``` 或者在每個命令上運行的 hooks: - `eask-before-command-hook` - `eask-after-command-hook` ```elisp (add-hook 'eask-before-command-hook (lambda () (message "%s" (eask-command)))) ; print the current command ``` 對於包含空格的子命令,將與`/`連接: ```sh eask lint checkdoc # lint/checkdoc eask generate license # generate/license ``` 所以, ```elisp (add-hook 'eask-before-lint/checkdoc-hook (lambda () ;; 在 checkdoc linting 之前做一些事情... )) ``` # 📇 加入你自己的指令 您可以透過我們的 command 介面添加自己的命令: ```elisp (eask-defcommand my-test-command "測試指令印出無用的訊息。" (message "這是一個測試指令!")) ``` ================================================ FILE: docs/content/Getting-Started/Basic-Usage/_index.en.md ================================================ --- title: 🔨 Basic Usage weight: 250 --- Eask’s CLI is fully featured but simple to use, even for those who have very limited experience working from the command line. The following is a description of the most common commands you will use while developing your Eask project. See the [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/) for a comprehensive view of Eask’s CLI. Once you have installed [Eask][], make sure it is in your `PATH`. You can test that Eask has been installed correctly via the help command: ``` eask --help ``` {{< hint ok >}} 💡 Optionally, you can use `--show-hidden` to show all available commands and options! {{< /hint >}} The output you see in your console should be similar to the following: ```text eask is the main command, used to manage your Emacs dependencies Eask is a command-line tool that helps you build, lint, and test Emacs Lisp packages. Usage: eask [options..] Commands: analyze [files..] Run Eask checker archives List out all package archives [aliases: sources] clean Delete various files produced during building compile [names..] Byte-compile `.el' files create Create a new elisp project docker [args..] Launch specified Emacs version in a Docker container docs Build documentation [aliases: doc] emacs [args..] Execute emacs with the appropriate environment eval [form] Evaluate lisp form with a proper PATH path [patterns..] Print the PATH (exec-path) from workspace [aliases: exec-path] exec [args..] Execute command with correct environment PATH set up files [patterns..] Print all package files format Run formatters [aliases: fmt] generate Generate files that are used for the development info Display information about the current package init [files..] Initialize project to use Eask install-deps Automatically install package dependencies [aliases: install-dependencies, prepare] install-file [files..] Install packages from files, .tar files, or directories install-vc [specs..] Fetch and install packages directly via version control install [names..] Install packages from archives or install from the workspace keywords Display the available keywords for use in the header section link Manage links lint Run linters list List all installed packages in dependency tree form load-path [patterns..] Print the load-path from workspace load [files..] Load elisp files outdated Show all outdated dependencies package-directory Print the path to package directory package [destination] Build a package artifact, and put it into the given destination [aliases: pack] recipe Suggest a recipe format recompile [names..] Byte-recompile `.el' files refresh Download descriptions of all configured package archives reinstall [names..] Reinstall packages from archives run Run custom tasks search [queries..] Search packages from archives status Show the workspace status test Run regression/unit tests uninstall [names..] Uninstall packages from archives [aliases: delete] upgrade [names..] Upgrade packages from archives locate Show the location where Eask is installed upgrade-eask Upgrade Eask itself [aliases: upgrade-self] Proxy Options: --proxy update proxy for HTTP and HTTPS to host [string] --http-proxy update proxy for HTTP to host [string] --https-proxy update proxy for HTTPS to host [string] --no-proxy set no-proxy to host [string] Options: --version output version information and exit [boolean] --help show usage instructions [boolean] --show-hidden Show hidden commands and options [boolean] -g, --global change default workspace to ~/.eask/ [boolean] -c, --config change default workspace to ~/.emacs.d/ [boolean] -a, --all enable all flag [boolean] -q, --quick start cleanly without loading the configuration files [boolean] -f, --force enable force flag [boolean] --debug turn on debug mode [boolean] --strict report error instead of warnings [boolean] --allow-error attempt to continue execution on error [boolean] --insecure allow insecure connection [boolean] --no-color enable/disable color output [boolean] -v, --verbose set verbosity from 0 to 5 [number] For more information, find the manual at https://emacs-eask.github.io/ ``` ## 🗃️ The `eask` Command The most common usage is probably to run eask with your current directory being the input directory. Then you run eask followed by a subcommand: ```sh eask info # Print out Eask-file information ``` Notice the subcommand can be nested: ```sh eask clean workspace # Deletes your `.eask` folder ``` Pass in option `--help` to look up more information regarding the command you are using: ```sh eask clean --help ``` The output, and it shows there are 7 subcommands supported: ```text Delete various files produced during building Usage: eask clean [options..] Commands: clean all Do all cleaning tasks [aliases: everything] clean autoloads Remove generated autoloads file clean dist [destination] Delete dist subdirectory [aliases: distribution] clean elc Remove byte compiled files generated by eask compile clean log-file Remove all generated log files clean pkg-file Remove generated pkg-file clean workspace Clean up .eask directory [aliases: .eask] Positionals: type of the cleaning task ... ``` Here is a list of known nested subcommands: - eask create - eask clean - eask generate - eask generate workflow - eask link - eask lint - eask run - eask source - eask test ## 📌 Knowing your `elpa` directory Eask creates an isolated environment, so it won't create any side effects after playing, testing, and running your elisp packages. But it's important to know what elpa directory (you can think of this as your `.emacs.d`) the current Eask session is pointing to, so you can release the full potential of this tool! Here is how Eask works behind the scene in different scenarios: | Name | Description | Options | Path | |--------|----------------------------------------------------------------------|--------------------|--------------| | local | The default behavior, use Eask as package dev tool | n/a | `./.eask` | | config | Use Eask as your package manager (It can be used as a test tool too) | `-c` or `--config` | `~/.emacs.d` | | global | Use Eask as a general tool, it's unrelated to other scopes | `-g` or `--global` | `~/.eask` | You might think of why these rules are created. It's easy to understand **config** and **local** scopes since many other build tools use the **local** scope to create an isolated environment. The **config** scope is an additional feature for people who prefer managing their packages with an external tool and not by built-in `package.el` or config base `straight.el`, so you can save up startup time to check if packages are installed for your Emacs to operate. So what is the **global** scope in terms of Eask? Why it's needed? Eask is more than a build tool now. Several commands don't require their dependencies as package dependencies. For example, the `cat` command: ```sh eask cat [PATTERNS..] ``` `cat` is a simple command that mimics Linux's default `cat` command, but it does the syntax highlighting for you! How it's implemented? The command relies on an external package [e2ansi][], and this is neither the `package` nor `config` dependency (it could be, but let's assume we don't want it). How do we use this command without side effects to your project or personal emacs configuration? The global scope is introduced for this problem. Now we can add any useful commands without worrying your environment got messed up. Here is the flowchart describes Eask's lifecycle:

By default, Eask uses your current directory as your workspace since most of the time you would just want to operate jobs for your elisp packages. [Eask]: https://github.com/emacs-eask/cli [e2ansi]: https://github.com/Lindydancer/e2ansi ================================================ FILE: docs/content/Getting-Started/Basic-Usage/_index.zh-tw.md ================================================ --- title: 🔨 使用基礎 weight: 250 --- Eask 的 CLI 功能齊全但易於使用,即使對於那些使用命令行的經驗非常有限的人也是如此。 以下是您在開發 Eask 項目時將使用的最常用命令的說明。 請參閱 [命令和選項](https://emacs-eask.github.io/Getting-Started/Commands-and-options/) 以全面了解 Eask 的 CLI。 一旦你安裝了 [Eask][],確保它在你的 `PATH` 中。 您可以通過 help 命令測試 Eask 是否已正確安裝: ``` eask --help ``` {{< hint ok >}} 💡 或者,您可以使用 `--show-hidden` 來顯示所有可用的命令和選項! {{< /hint >}} 您在控制台中看到的輸出應類似於以下內容: ```text eask is the main command, used to manage your Emacs dependencies Eask is a command-line tool that helps you build, lint, and test Emacs Lisp packages. Usage: eask [options..] Commands: analyze [files..] Run Eask checker archives List out all package archives [aliases: sources] clean Delete various files produced during building compile [names..] Byte-compile `.el' files create Create a new elisp project docker [args..] Launch specified Emacs version in a Docker container docs Build documentation [aliases: doc] emacs [args..] Execute emacs with the appropriate environment eval [form] Evaluate lisp form with a proper PATH path [patterns..] Print the PATH (exec-path) from workspace [aliases: exec-path] exec [args..] Execute command with correct environment PATH set up files [patterns..] Print all package files format Run formatters [aliases: fmt] generate Generate files that are used for the development info Display information about the current package init [files..] Initialize project to use Eask install-deps Automatically install package dependencies [aliases: install-dependencies, prepare] install-file [files..] Install packages from files, .tar files, or directories install-vc [specs..] Fetch and install packages directly via version control install [names..] Install packages from archives or install from the workspace keywords Display the available keywords for use in the header section link Manage links lint Run linters list List all installed packages in dependency tree form load-path [patterns..] Print the load-path from workspace load [files..] Load elisp files outdated Show all outdated dependencies package-directory Print the path to package directory package [destination] Build a package artifact, and put it into the given destination [aliases: pack] recipe Suggest a recipe format recompile [names..] Byte-recompile `.el' files refresh Download descriptions of all configured package archives reinstall [names..] Reinstall packages from archives run Run custom tasks search [queries..] Search packages from archives status Show the workspace status test Run regression/unit tests uninstall [names..] Uninstall packages from archives [aliases: delete] upgrade [names..] Upgrade packages from archives locate Show the location where Eask is installed upgrade-eask Upgrade Eask itself [aliases: upgrade-self] Proxy Options: --proxy update proxy for HTTP and HTTPS to host [string] --http-proxy update proxy for HTTP to host [string] --https-proxy update proxy for HTTPS to host [string] --no-proxy set no-proxy to host [string] Options: --version output version information and exit [boolean] --help show usage instructions [boolean] --show-hidden Show hidden commands and options [boolean] -g, --global change default workspace to ~/.eask/ [boolean] -c, --config change default workspace to ~/.emacs.d/ [boolean] -a, --all enable all flag [boolean] -q, --quick start cleanly without loading the configuration files [boolean] -f, --force enable force flag [boolean] --debug turn on debug mode [boolean] --strict report error instead of warnings [boolean] --allow-error attempt to continue execution on error [boolean] --insecure allow insecure connection [boolean] --no-color enable/disable color output [boolean] -v, --verbose set verbosity from 0 to 5 [number] For more information, find the manual at https://emacs-eask.github.io/ ``` ## 🗃️ `eask` 命令 最常見的用法可能是在當前目錄作為輸入目錄的情況下運行 eask。 然後你運行 eask 後跟一個子命令: ```sh eask info # 打印出 Eask 文件信息 ``` Notice the subcommand can be nested: ```sh eask clean workspace # 刪除你的 .eask 文件夾 ``` 傳遞選項 `--help` 以查找有關您正在使用的命令的更多信息: ```sh eask clean --help ``` 輸出,它顯示支持 7 個子命令: ```text Delete various files produced during building Usage: eask clean [options..] Commands: clean all Do all cleaning tasks [aliases: everything] clean autoloads Remove generated autoloads file clean dist [destination] Delete dist subdirectory [aliases: distribution] clean elc Remove byte compiled files generated by eask compile clean log-file Remove all generated log files clean pkg-file Remove generated pkg-file clean workspace Clean up .eask directory [aliases: .eask] Positionals: type of the cleaning task ... ``` 以下是已知的嵌套子命令列表: - eask create - eask clean - eask generate - eask generate workflow - eask link - eask lint - eask run - eask source - eask test ## 📌 了解你的 `elpa` 目錄 Eask 創建了一個隔離的環境,因此在播放、測試和運行您的 elisp 包後它不會產生任何副作用。 但了解當前 Eask 會話指向的 elpa 目錄(您可以將其視為您的 `.emacs.d`)非常重要,這樣您 才能釋放該工具的全部潛力! 以下是 Eask 在不同場景下的幕後工作方式: | 名稱 | 描述 | 選項 | 路徑 | |--------|---------------------------------------------------|--------------------|--------------| | local | 默認行為,使用 Eask 作為包開發工具 | n/a | `./.eask` | | config | 使用 Eask 作為您的包管理器 (它也可以用作測試工具) | `-c` or `--config` | `~/.emacs.d` | | global | Eask 作為通用工具使用,與其他範圍無關 | `-g` or `--global` | `~/.eask` | 您可能會想到為什麼要創建這些規則。 **config** 和 **local** 範圍很容易理解,因為許多其他構建工具使用 **local** 範圍來創建隔離環境。 **config** 範圍是一項附加功能,適用於喜歡使用外部工具而不是內置 `package.el` 或配置基礎 `straight.el` 管理包的人, 因此您可以節省啟動時間 檢查是否為您的 Emacs 運行安裝了軟件包。 那麼 Eask 的 **global** 範圍是什麼? 為什麼需要它? Eask 現在不僅僅是一個構建工具。 一些命令不需要它們的依賴項作為包依賴項。 例如,`cat` 命令: ```sh eask cat [PATTERNS..] ``` `cat` 是一個模仿 Linux 的默認 `cat` 命令的簡單命令,但它會為您突出顯示語法! 它是如何實施的? 該命令依賴於外部包 [e2ansi][],這既不是 `package` 也不是 `config` 依賴項(它可能是,但假設我們不需要它)。 我們如何使用這個命令而不會對您的項目或個人 emacs 配置產生副作用? 針對這個問題引入了全局範圍。 現在我們可以添加任何有用的命令而不用擔心你的環境被搞砸了。 下面是描述 Eask 生命週期的流程圖:

默認情況下,Eask 使用您的當前目錄作為您的工作區,因為大多數時候您只想為您的 elisp 包運行作業。 [Eask]: https://github.com/emacs-eask/cli [e2ansi]: https://github.com/Lindydancer/e2ansi ================================================ FILE: docs/content/Getting-Started/Commands-and-options/_index.en.md ================================================ --- title: 🚩 Commands and options weight: 300 --- {{< toc >}} The general syntax of the **eask** program is: ```sh eask [GLOBAL-OPTIONS] [COMMAND] [COMMAND-OPTIONS] [COMMAND-ARGUMENTS] ``` # 🚩 Creating ## 🔍 eask create package Create a new elisp project with the default `Eask`-file and CI/CD support. ```sh eask [GLOBAL-OPTIONS] create package ``` {{< hint info >}} 💡 The template project is located in https://github.com/emacs-eask/template-elisp {{< /hint >}} ## 🔍 eask create elpa Create a new ELPA using [github-elpa](https://github.com/10sr/github-elpa). ```sh eask [GLOBAL-OPTIONS] create elpa ``` {{< hint info >}} 💡 The template project is located in https://github.com/emacs-eask/template-elpa {{< /hint >}} ## 🔍 eask create el-project Create a new project with [el-project](https://github.com/Kyure-A/el-project). ```sh eask [GLOBAL-OPTIONS] create el-project ``` # 🚩 Core Often use commands that are uncategorized. ## 🔍 eask init Initialize the current directory to start using Eask. ```sh eask [GLOBAL-OPTIONS] init ``` Eask will generate the file like this: ```elisp (package "PACKAGE-NAME" "VERSION" "YOUR PACKAGE SUMMARY") (website-url "https://example.com/project-url/") (keywords "KEYWORD1" "KEYWORD2") (package-file "PACKAGE-FILE") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ``` **[RECOMMENDED]** If you already have an elisp project, you can convert the `.el` file to Eask-file: ```sh eask init --from source /path/to/source.el ``` If you already have a [Cask][] project, you can convert Cask-file to Eask-file: ```sh eask init --from cask /path/to/Cask ``` If you already have a [Eldev][] project, you can convert Eldev-file to Eask-file: ```sh eask init --from eldev /path/to/Eldev ``` If you already have a [Keg][] project, you can convert Keg-file to Eask-file: ```sh eask init --from keg /path/to/Keg ``` {{< hint ok >}} 💡 See section [Examples](https://emacs-eask.github.io/examples) for more Eask-file examples! {{< /hint >}} ## 🔍 eask info Show information about the project or configuration. ```sh eask [GLOBAL-OPTIONS] info ``` ## 🔍 eask status Show the workspace status. ```sh eask [GLOBAL-OPTIONS] status ``` ## 🔍 eask install To install packages from archives or install from the workspace. ```sh eask [GLOBAL-OPTIONS] install [PACKAGES..] ``` Install packages by specifying arguments: ```sh eask install auto-complete helm magit ``` Or else, it will install the package from the current development: ```sh eask install ``` ## 🔍 eask install-deps To install all dependencies. Alias: `install-dependencies`, `prepare` ```sh eask [GLOBAL-OPTIONS] install-deps [--dev] ``` {{< hint ok >}} 💡 Specify option [--dev] to install dependencies from the development scope. {{< /hint >}} ## 🔍 eask install-file Install packages from files, `.tar` files, or directories. ```sh eask [GLOBAL-OPTIONS] install-file [FILES..] ``` ## 🔍 eask install-vc Fetch and install packages directly via version control. ```sh eask [GLOBAL-OPTIONS] install-vc [SPECS..] ``` ## 🔍 eask uninstall To uninstall/delete packages. ```sh eask [GLOBAL-OPTIONS] uninstall [PACKAGES..] ``` Uninstall packages by specifying arguments: ```sh eask uninstall dash f s ``` Or else, it will uninstall the package from the current workspace: ```sh eask uninstall ``` ## 🔍 eask reinstall To reinstall packages from archives. ```sh eask [GLOBAL-OPTIONS] reinstall [PACKAGES..] ``` ## 🔍 eask package Build the package artifact. Alias: `pack` ```sh eask package [DESTINATION] ``` If [DESTINATION] is not specified, it will generate to the `/dist` folder by default. ## 🔍 eask compile Byte-compile `.el` files. ```sh eask compile [FILES..] ``` Compile files by specifying arguments: ```sh eask compile file-1.el file-2.el ``` Or compile files that are already specified in your `Eask`-file. ```sh eask compile ``` ## 🔍 eask recompile Byte-recompile `.el` files. ```sh eask recompile [FILES..] ``` {{< hint info >}} 💡 Similar to `eask compile`, but it will also remove old `.elc` files before compiling. {{< /hint >}} ## 🔍 eask package-directory Print the path to package directory, where all dependencies are installed. ```sh eask [GLOBAL-OPTIONS] package-directory ``` ## 🔍 eask path Print the `PATH` environment variable of this project. Alias: `exec-path` ```sh eask [GLOBAL-OPTIONS] path [PATTERNS..] ``` Optionally, you can pass in `[PATTERNS..]` to perform the search. ## 🔍 eask load-path Print the load path containing the dependencies of the current project. ```sh eask [GLOBAL-OPTIONS] load-path [PATTERNS..] ``` Optionally, you can pass in `[PATTERNS..]` to perform the search. ## 🔍 eask files Print the list of all package files. ```sh eask [GLOBAL-OPTIONS] files [PATTERNS..] ``` If `[PATTERNS..]` are defined, it will display files that match that pattern. ## 🔍 eask recipe Suggest a recipe format. ```sh eask [GLOBAL-OPTIONS] recipe [FILES..] ``` ## 🔍 eask keywords Display the available keywords for use in the header section. ```sh eask [GLOBAL-OPTIONS] keywords ``` ## 🔍 eask bump Bump version for your project and/or Eask-file. ```sh eask [GLOBAL-OPTIONS] bump [LEVELS..] ``` {{< hint info >}} 💡 Argument **[LEVELS..]** accepts **major**, **minor** and/or **patch**! {{< /hint >}} ## 🔍 eask cat View filename(s). The positional argument `[PATTERNS..]` is an array of wildcard patterns. ```sh eask [GLOBAL-OPTIONS] cat [PATTERNS..] ``` {{< hint info >}} 💡 This command uses the package [e2ansi](https://github.com/Lindydancer/e2ansi) to accomplish the syntax highlighting. {{< /hint >}} ## 🔍 eask concat Concatenate all Emacs Lisp files into one file. ```sh eask [GLOBAL-OPTIONS] concat [FILES..] ``` ## 🔍 eask loc Print LOC information. ```sh eask [GLOBAL-OPTIONS] loc [FILES..] ``` # 🚩 Documentation Commands used to build documentation site. ## 🔍 eask docs el2org Build documentation with [el2org][]. ```sh eask [GLOBAL-OPTIONS] docs el2org [NAMES..] ``` # 🚩 Execution Commands allow you to execute on top of the Eask core. Basically, this allows you to do anything you want! ## 🔍 eask load Load Emacs Lisp files in order. ```sh eask [GLOBAL-OPTIONS] load [FILES..] ``` ## 🔍 eask exec Execute the system command with the given arguments. ```sh eask [GLOBAL-OPTIONS] exec [COMMAND] [ARGUMENTS ...] ``` ## 🔍 eask emacs Execute emacs with the appropriate environment. ```sh eask [GLOBAL-OPTIONS] emacs [ARGUMENTS ...] ``` ## 🔍 eask eval Evaluate `FORM` as a lisp form. ```sh eask [GLOBAL-OPTIONS] eval [FORM] ``` ## 🔍 eask repl Start an Elisp REPL. ```sh eask [GLOBAL-OPTIONS] repl [FILES..] ``` Alias: `ielm` ## 🔍 eask run script Run the script. ```sh eask [GLOBAL-OPTIONS] run script [NAMES..] ``` ## 🔍 eask run command Run the command. Alias: `cmd` ```sh eask [GLOBAL-OPTIONS] run command [NAMES..] ``` ## 🔍 eask docker Launch specified Emacs version in a Docker container. ```sh eask [GLOBAL-OPTIONS] docker [ARGUMENTS ...] ``` For example: ```sh eask docker 26.1 info ``` This is the same as jumping right into Emacs 26.1 (in docker) and executing `eask info`. # 🚩 Management Commands that help you manage your package's dependencies. ## 🔍 eask archives List out all package archives. ```sh eask [GLOBAL-OPTIONS] archives ``` ## 🔍 eask search Search packages from archives. ```sh eask [GLOBAL-OPTIONS] search [QUEIRES..] ``` ## 🔍 eask upgrade Upgrade all packages from archives. ```sh eask [GLOBAL-OPTIONS] upgrade ``` ## 🔍 eask list List all installed packages in dependency tree form. ```sh eask [GLOBAL-OPTIONS] list [--depth] ``` ## 🔍 eask outdated List out all outdated packages. ```sh eask [GLOBAL-OPTIONS] outdated [--depth] ``` ## 🔍 eask refresh Download descriptions of all configured package archives. ```sh eask [GLOBAL-OPTIONS] refresh ``` # 🚩 Generating Generate files that are used for the development. ## 🔍 eask generate autoloads Generate the autoload file. Write a package autoloads to `project-autoloads.el` in the project root. ```sh eask [GLOBAL-OPTIONS] generate autoloads ``` `project` is the project name, as declared in `Eask`-file. See [Multi-file Packages (elisp)](https://www.gnu.org/software/emacs/manual/html_node/elisp/Multi_002dfile-Packages.html#Multi_002dfile-Packages) for details. ## 🔍 eask generate pkg-file Generate the pkg file. Write a package descriptor file to `project-pkg.el` in the project root. Alias: `pkg`, `pkg-el` ```sh eask [GLOBAL-OPTIONS] generate pkg-file ``` `project` is the project name, as declared in `Eask`-file. See [Multi-file Packages (elisp)](https://www.gnu.org/software/emacs/manual/html_node/elisp/Multi_002dfile-Packages.html#Multi_002dfile-Packages) for details. ## 🔍 eask generate recipe Generate the recipe file. ```sh eask [GLOBAL-OPTIONS] generate recipe [DESTINATION] ``` If [DESTINATION] is not specified, it will generate to the `/recipes` folder by default. ## 🔍 eask generate license Generate a LICENSE file. ```sh eask [GLOBAL-OPTIONS] generate license ``` `name` is the type of the license, see https://api.github.com/licenses for all the choices. {{< hint info >}} 💡 This command uses the package [license-templates](https://github.com/jcs-elpa/license-templates) to generate ignore file. {{< /hint >}} ## 🔍 eask generate ignore Generate an ignore file. ```sh eask [GLOBAL-OPTIONS] generate ignore ``` {{< hint info >}} 💡 This command uses the package [gitignore-templates](https://github.com/xuchunyang/gitignore-templates.el) to generate ignore file. {{< /hint >}} ## 🔍 eask generate test ert Create a new test project for the [ert][] tests. ```sh eask [GLOBAL-OPTIONS] generate test ert [NAMES..] ``` ## 🔍 eask generate test ert-runner Create a new test project for the [ert-runner][]. ```sh eask [GLOBAL-OPTIONS] generate test ert-runner [NAMES..] ``` ## 🔍 eask generate test buttercup Create a new [Buttercup][] setup for the project. ```sh eask [GLOBAL-OPTIONS] generate test buttercup ``` ## 🔍 eask generate test ecukes Create a new [Ecukes][] setup for the project. ```sh eask [GLOBAL-OPTIONS] generate test ecukes ``` ## 🔍 eask generate workflow circle-ci Generate the [CircleCI][] workflow yaml file. The default filename is `config.yml`. ```sh eask [GLOBAL-OPTIONS] generate workflow circle-ci [--file] ``` This will generate the yaml file under `.circleci/`! ## 🔍 eask generate workflow github Generate the [GitHub Actions][] workflow yaml file. The default filename is `test.yml`. ```sh eask [GLOBAL-OPTIONS] generate workflow github [--file] ``` This will generate the yaml file under `.github/workflow/`! ## 🔍 eask generate workflow gitlab Generate the [GitLab Runner][] workflow yaml file. The default filename is `.gitlab-ci.yml`. ```sh eask [GLOBAL-OPTIONS] generate workflow gitlab [--file] ``` ## 🔍 eask generate workflow travis-ci Generate the [Travis CI][] workflow yaml file. The default filename is `.travis.yml`. ```sh eask [GLOBAL-OPTIONS] generate workflow travis-ci [--file] ``` # 🚩 Linking Link between this package and a dependency on the local filesystem. A linked dependency avoids the need to download a dependency from a remote archive. The package linked to must either have a `Eask`-file or a `-pkg.el`-file. ## 🔍 eask link add Links the given *source* directory into the package directory of this project, under the given *package* name. ```sh eask [GLOBAL-OPTIONS] link add ``` ## 🔍 eask link delete Delete locally linked packages. Alias: `remove` ```sh eask [GLOBAL-OPTIONS] link delete [NAMES..] ``` ## 🔍 eask link list List all links. ```sh eask [GLOBAL-OPTIONS] link list ``` # 🚩 Cleaning Delete various files produced during building. ## 🔍 eask clean workspace Delete the `.eask` from the current workspace. Alias: `.eask` ```sh eask [GLOBAL-OPTIONS] clean workspace ``` ⛔️ Don't specify the option `--config, -c`, or else it will delete your entire `~/.emacs.d`. ```elisp eask clean workspace -g ``` ## 🔍 eask clean elc Delete all `.elc` files. This would respect to your `Eask` file. ```sh eask [GLOBAL-OPTIONS] clean elc ``` ## 🔍 eask clean dist Delete the `dist` directory where the build output is stored. Alias: `distribution` ```sh eask [GLOBAL-OPTIONS] clean dist ``` ## 🔍 eask clean autoloads Remove the generated autoloads file. ```sh eask [GLOBAL-OPTIONS] clean autoloads ``` ## 🔍 eask clean pkg-file Remove the generated pkg-file. ```sh eask [GLOBAL-OPTIONS] clean pkg-file ``` ## 🔍 eask clean log-file Remove all generated log files. ```sh eask [GLOBAL-OPTIONS] clean log-file ``` ## 🔍 eask clean all This command is the combination of all other clean commands. - `clean workspace` - `clean elc` - `clean dist` - `clean autoloads` - `clean pkg-file` - `clean log-file` Alias: `everything` ```sh eask [GLOBAL-OPTIONS] clean all ``` # 🚩 Linting Commands that lint your Emacs package. ## 🔍 eask lint package Run [package-lint](https://github.com/purcell/package-lint). ```sh eask [GLOBAL-OPTIONS] lint package [FILES..] ``` ## 🔍 eask lint checkdoc Run checkdoc (built-in). ```sh eask [GLOBAL-OPTIONS] lint checkdoc [FILES..] ``` ## 🔍 eask lint elint Run elint (built-in). ```sh eask [GLOBAL-OPTIONS] lint elint [FILES..] ``` ## 🔍 eask lint elisp-lint Run [elisp-lint](https://github.com/gonewest818/elisp-lint). ```sh eask [GLOBAL-OPTIONS] lint elisp-lint [FILES..] ``` This does respect the `.dir-locals.el` file! 🎉 ## 🔍 eask lint elsa Run [elsa](https://github.com/emacs-elsa/Elsa). ```sh eask [GLOBAL-OPTIONS] lint lint elsa [FILES..] ``` ## 🔍 eask lint indent Run indent-lint. ```sh eask [GLOBAL-OPTIONS] lint indent [FILES..] ``` ## 🔍 eask lint declare Run check-declare (built-in). ```sh eask [GLOBAL-OPTIONS] lint declare [FILES..] ``` ## 🔍 eask lint regexps Run [relint](https://github.com/mattiase/relint). Alias: `lint relint` ```sh eask [GLOBAL-OPTIONS] lint regexps [FILES..] ``` ## 🔍 eask lint keywords Run keywords checker (built-in). ```sh eask [GLOBAL-OPTIONS] lint keywords ``` ## 🔍 eask lint license Run license check. ```sh eask [GLOBAL-OPTIONS] lint license ``` ## 🔍 eask lint org Run `org-lint` on Org files. ```sh eask [GLOBAL-OPTIONS] lint org [FILES..] ``` # 🚩 Testing Run regression/unit tests. ## 🔍 eask test activate Activate package; use to test the package activation ```sh eask [GLOBAL-OPTIONS] activate [FILES..] ``` {{< hint info >}} 💡 You can pass in **[FILES..]** so you can test your package activation fully! **[FILES..]** will be loaded after the package is activated. {{< /hint >}} ## 🔍 eask test ert Run [ert][] tests. ```sh eask [GLOBAL-OPTIONS] test ert [FILES..] ``` ## 🔍 eask test ert-runner Run [ert][] test using [ert-runner][]. ```sh eask [GLOBAL-OPTIONS] test ert-runner ``` ## 🔍 eask test buttercup Run [buttercup][] tests. ```sh eask [GLOBAL-OPTIONS] test buttercup ``` ## 🔍 eask test ecukes Run [ecukes][] tests. ```sh eask [GLOBAL-OPTIONS] test ecukes [FILES..] ``` ## 🔍 eask test melpazoid Run [melpazoid][] tests. ```sh eask [GLOBAL-OPTIONS] test melpazoid [DIRECTORIES..] ``` {{< hint info >}} 💡 If **[DIRECTORIES..]** is not passed in; it will use the current workspace instead. {{< /hint >}} # 🚩 Formatting Commands that formats your Emacs source files. ## 🔍 eask format elisp-autofmt Run [elisp-autofmt][] formatter. ```sh eask [GLOBAL-OPTIONS] format elisp-autofmt [FILES..] ``` ## 🔍 eask format elfmt Run [elfmt][] formatter. ```sh eask [GLOBAL-OPTIONS] format elfmt [FILES..] ``` # 🚩 Control DSL List of commands that control DSL. ## 🔍 eask source add Add an archive source. ```sh eask [GLOBAL-OPTIONS] source add [URL] ``` ## 🔍 eask source delete Remove an archive source. Alias: `remove` ```sh eask [GLOBAL-OPTIONS] source delete ``` ## 🔍 eask source list List all source information. ```sh eask [GLOBAL-OPTIONS] source list ``` {{< hint info >}} 💡 This command is the same as `eask archives`! {{< /hint >}} # 🚩 Utilities Other helper commands. ## 🔍 eask upgrade-eask Upgrade Eask to the latest version. Alias: `upgrade-self` ```sh eask [GLOBAL-OPTIONS] upgrade-eask ``` {{< hint warning >}} 💡 This will only work if you install it from the source! {{< /hint >}} ## 🔍 eask locate Show the location where Eask is installed. ```sh eask [GLOBAL-OPTIONS] locate ``` ## 🔍 eask root Display the effective installation directory of your Emacs packages. ```sh eask [GLOBAL-OPTIONS] root ``` # 🚩 Checker Commands to check your Eask-file. ## 🔍 eask analyze Lint an `Eask`-file. ```sh eask [GLOBAL-OPTIONS] analyze [FILES..] ``` Example: ```bash # lint all Eask-files in the current directory and subdirectories eask analyze # lint specific files eask analyze Eask Eask.27 # lint all Eask-files in specified directory and subdirectories eask analyze src/ # print result as JSON eask analyze --json ``` For more detail, run `eask analyze --help`. # 🚩 Global Options The following options are available on all Eask commands: ## 🔍 --global, -g This will use `~/.eask/` instead of the package development environment. This is used for other tasks. e.g., `cat`, etc. ```sh eask -g [COMMAND] ``` ## 🔍 --config, -c This will use `~/.emacs.d/` instead of the package development environment. This is used for doing stuff for your **Emacs configuration**. e.g., package management, etc. ```sh eask -c [COMMAND] ``` ## 🔍 --all, -a Enable the `all` flag. ```sh eask -a [COMMAND] ``` ## 🔍 --quick, -q Start cleanly without loading the configuration files. ```sh eask -q [COMMAND] ``` ## 🔍 --force, -f Force command's execution. Force to uninstall the package `dash` even it's a dependency from another packages. ```sh eask -f [COMMAND] ``` ## 🔍 --debug Enable debug information. This is equivalent to: ```elisp (setq debug-on-error t) ``` ## 🔍 --strict Trigger error instead of warnings. For instance, in **eask compile**: ```elisp (setq byte-compile-error-on-warn t) ``` ## 🔍 --allow-error Continue the execution without killing the Emacs. ## 🔍 --insecure Connect archives with HTTP instead of HTTPS. ## 🔍 --timestamps Enable/Disable timestamps. ## 🔍 --log-level Enable/Disable log header. ## 🔍 --log-file, --lf Weather to generate log files. ## 🔍 --no-color Disable color output. ## 🔍 --elapsed-time, --et Show elapsed time between each operation. ## 🔍 --verbose, -v `` Set verbosity from 0 to 5. ```sh eask --verbose 4 [COMMAND] ``` ## 🔍 --version Show version number. ## 🔍 --help Show help. # 🚩 Proxy Options ## 🔍 --proxy `` Set Emacs proxy for HTTP and HTTPS: ```sh eask --proxy "localhost:8888" [COMMAND] ``` ## 🔍 --http-proxy `` Set Emacs proxy for HTTP only. ## 🔍 --https-proxy `` Set Emacs proxy for HTTPS only. ## 🔍 --no-proxy `` Do not use a proxy for any URL matching pattern. ``is an Emacs regular expression. [Cask]: https://github.com/cask/cask [Eldev]: https://emacs-eldev.github.io/eldev/ [Keg]: https://github.com/conao3/keg.el [CircleCI]: https://circleci.com/ [GitHub Actions]: https://github.com/features/actions [GitLab Runner]: https://docs.gitlab.com/runner/ [Travis CI]: https://www.travis-ci.com/ [el2org]: https://github.com/tumashu/el2org [ert]: https://www.gnu.org/software/emacs/manual/html_node/ert/ [ert-runner]: https://github.com/rejeep/ert-runner.el [buttercup]: https://github.com/jorgenschaefer/emacs-buttercup [ecukes]: https://github.com/ecukes/ecukes [melpazoid]: https://github.com/riscy/melpazoid [elisp-autofmt]: https://codeberg.org/ideasman42/emacs-elisp-autofmt [elfmt]: https://github.com/riscy/elfmt ================================================ FILE: docs/content/Getting-Started/Commands-and-options/_index.zh-tw.md ================================================ --- title: 🚩 命令和選項 weight: 300 --- {{< toc >}} **eask** 程序的一般語法是: ```sh eask [GLOBAL-OPTIONS] [COMMAND] [COMMAND-OPTIONS] [COMMAND-ARGUMENTS] ``` # 🚩 創建 ## 🔍 eask create package 使用默認的 `Eask` 文件和 CI/CD 支持創建一個新的 elisp 項目。 ```sh eask [GLOBAL-OPTIONS] create package ``` {{< hint info >}} 💡 模板項目位於 https://github.com/emacs-eask/template-elisp。 {{< /hint >}} ## 🔍 eask create elpa 使用 [github-elpa](https://github.com/10sr/github-elpa) 創建一個新的 ELPA。 ```sh eask [GLOBAL-OPTIONS] create elpa ``` {{< hint info >}} 💡 模板項目位於 https://github.com/emacs-eask/template-elpa。 {{< /hint >}} ## 🔍 eask create el-project 使用 [el-project](https://github.com/Kyure-A/el-project) 創建一個新專案。 ```sh eask [GLOBAL-OPTIONS] create el-project ``` # 🚩 核心 經常使用未分類的命令。 ## 🔍 eask init 初始化當前目錄以開始使用 Eask。 ```sh eask [GLOBAL-OPTIONS] init ``` Eask 將生成這樣的文件: ```elisp (package "PACKAGE-NAME" "VERSION" "YOUR PACKAGE SUMMARY") (website-url "https://example.com/project-url/") (keywords "KEYWORD1" "KEYWORD2") (package-file "PACKAGE-FILE") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ``` **[推薦]** 如果您已有 elisp 項目,您可以將 `.el` 文件轉換為 Eask 文件: ```sh eask init --from source /path/to/source.el ``` 如果您已有 [Cask][] 項目,您可以將 Cask 文件轉換為 Eask 文件: ```sh eask init --from cask /path/to/Cask ``` 如果您已有 [Eldev][] 項目,您可以將 Eldev 文件轉換為 Eask 文件: ```sh eask init --from eldev /path/to/Eldev ``` 如果您已有 [Keg][] 項目,您可以將 Keg 文件轉換為 Eask 文件: ```sh eask init --from keg /path/to/Keg ``` {{< hint ok >}} 💡 有關更多 Eask 文件示例,請參閱[示例](https://emacs-esk.github.io/examples)部分! {{< /hint >}} ## 🔍 eask info 顯示有關項目或配置的信息。 ```sh eask [GLOBAL-OPTIONS] info ``` ## 🔍 eask status 顯示現在 workspace 的狀態。 ```sh eask [GLOBAL-OPTIONS] status ``` ## 🔍 eask install 從包源或 workspace 安裝包。 ```sh eask [GLOBAL-OPTIONS] install [PACKAGES..] ``` 通過指定參數安裝包: ```sh eask install auto-complete helm magit ``` 否則,它將安裝當前開發的包: ```sh eask install ``` ## 🔍 eask install-deps 安裝所有依賴項。 別名: `install-dependencies`, `prepare` ```sh eask [GLOBAL-OPTIONS] install-deps [--dev] ``` {{< hint ok >}} 💡 指定選項 [--dev] 從開發範圍安裝依賴項。 {{< /hint >}} ## 🔍 eask install-file 從檔案、`.tar` 檔案或目錄安裝套件。 ```sh eask [GLOBAL-OPTIONS] install-file [FILES..] ``` ## 🔍 eask install-vc 直接透過版本控制擷取及安裝套件。 ```sh eask [GLOBAL-OPTIONS] install-vc [SPECS..] ``` ## 🔍 eask uninstall 卸載/刪除包。 ```sh eask [GLOBAL-OPTIONS] uninstall [PACKAGES..] ``` 通過指定參數卸載軟件包: ```sh eask uninstall dash f s ``` 否則,它將從當前開發中卸載包: ```sh eask uninstall ``` ## 🔍 eask reinstall 重新安裝包. ```sh eask [GLOBAL-OPTIONS] reinstall [PACKAGES..] ``` ## 🔍 eask package 構建包工件。 別名: `pack` ```sh eask package [DESTINATION] ``` 如果未指定 [DESTINATION],則默認導出到 `/dist` 文件夾。 ## 🔍 eask compile 字節編譯 `.el` 文件。 ```sh eask compile [FILES..] ``` 通過指定參數編譯文件: ```sh eask compile file-1.el file-2.el ``` 或者編譯已經在你的 `Eask` 文件中指定的文件。 ```sh eask compile ``` ## 🔍 eask recompile 重新字節編譯 `.el` 文件。 ```sh eask recompile [FILES..] ``` {{< hint info >}} 💡 與 `eask compile` 類似,但它在編譯前會刪除舊的 `.elc` 檔案。 {{< /hint >}} ## 🔍 eask package-directory 打印包目錄的路徑,其中安裝了所有依賴項。 ```sh eask [GLOBAL-OPTIONS] package-directory ``` ## 🔍 eask path 打印此項目的 `PATH` 環境變量。 別名: `exec-path` ```sh eask [GLOBAL-OPTIONS] path [PATTERNS..] ``` 或者,您可以傳入 `[PATTERNS..]` 來執行搜索。 ## 🔍 eask load-path 打印包含當前項目依賴項的加載路徑。 ```sh eask [GLOBAL-OPTIONS] load-path [PATTERNS..] ``` 或者,您可以傳入 `[PATTERNS..]` 來執行搜索。 ## 🔍 eask files 打印所有包文件的列表。 ```sh eask [GLOBAL-OPTIONS] files [PATTERNS..] ``` 如果定義了 `[PATTERNS..]` ,它將顯示與該模式匹配的文件。 ## 🔍 eask recipe 建議 recipe 格式。 ```sh eask [GLOBAL-OPTIONS] recipe [FILES..] ``` ## 🔍 eask keywords 顯示可在標頭部分使用的關鍵字。 ```sh eask [GLOBAL-OPTIONS] keywords ``` ## 🔍 eask bump 為你的專案或 Eask-file 遞增版本號。 ```sh eask [GLOBAL-OPTIONS] bump [LEVELS..] ``` {{< hint info >}} 💡 參數 **[LEVELS..]** 接受 **major**、**minor** 和/或 **patch**! {{< /hint >}} ## 🔍 eask cat 查看文件名。 位置參數 `[PATTERNS..]` 是一個通配符模式數組。 ```sh eask [GLOBAL-OPTIONS] cat [PATTERNS..] ``` {{< hint info >}} 💡 此命令使用包 [e2ansi](https://github.com/Lindydancer/e2ansi) 來完成語法高亮。 {{< /hint >}} ## 🔍 eask concat 將所有 Emacs Lisp 文件連接成一個文件。 ```sh eask [GLOBAL-OPTIONS] concat [FILES..] ``` ## 🔍 eask loc 列印 LOC 信息。 ```sh eask [GLOBAL-OPTIONS] loc [FILES..] ``` # 🚩 文件 用於建立文檔站點的命令。 ## 🔍 eask docs el2org 使用 [el2org][] 建置文檔。 ```sh eask [GLOBAL-OPTIONS] docs el2org [NAMES..] ``` # 🚩 執行 指令允許執行在 Eask 核心之上。 基本上,這可以讓你做任何你想做的事! ## 🔍 eask load 按順序加載 Emacs Lisp 文件。 ```sh eask [GLOBAL-OPTIONS] load [FILES..] ``` ## 🔍 eask exec 使用給定的參數執行系統命令。 ```sh eask [GLOBAL-OPTIONS] exec [COMMAND] [ARGUMENTS ...] ``` ## 🔍 eask emacs 在合適的環境下執行emacs。 ```sh eask [GLOBAL-OPTIONS] emacs [ARGUMENTS ...] ``` ## 🔍 eask eval 將 `FORM` 評估為 lisp 形式。 ```sh eask [GLOBAL-OPTIONS] eval [FORM] ``` ## 🔍 eask repl 啟動一個 Elisp REPL。 ```sh eask [GLOBAL-OPTIONS] repl [FILES..] ``` 別名: `ielm` ## 🔍 eask run script 運行腳本。 ```sh eask [GLOBAL-OPTIONS] run script [NAMES..] ``` ## 🔍 eask run command 運行指令。 別名: `cmd` ```sh eask [GLOBAL-OPTIONS] run command [NAMES..] ``` ## 🔍 eask docker 在 Docker 容器中啟動指定的 Emacs 版本 ```sh eask [GLOBAL-OPTIONS] docker [ARGUMENTS ...] ``` 例如: ```sh eask docker 26.1 info ``` 這與直接跳入 Emacs 26.1(在 docker 中)並執行 `eask info` 相同。 # 🚩 管理 指令能幫助你管理套件依賴. ## 🔍 eask archives 列出所有包源。 ```sh eask [GLOBAL-OPTIONS] archives ``` ## 🔍 eask search 從包源中搜索包。 ```sh eask [GLOBAL-OPTIONS] search [QUEIRES..] ``` ## 🔍 eask upgrade 升級所有軟件包。 ```sh eask [GLOBAL-OPTIONS] upgrade ``` ## 🔍 eask list 使用樹狀依賴圖列出所有已安裝的包。 ```sh eask [GLOBAL-OPTIONS] list [--depth] ``` ## 🔍 eask outdated 列出所有過時的包。 ```sh eask [GLOBAL-OPTIONS] outdated [--depth] ``` ## 🔍 eask refresh 刷新包源。 ```sh eask [GLOBAL-OPTIONS] refresh ``` # 🚩 生成 生成用於開發的文件。 ## 🔍 eask generate autoloads 生成一個 autoloads 文件。 將包自動加載到項目根目錄中的 `project-autoloads.el`。 ```sh eask [GLOBAL-OPTIONS] generate autoloads ``` `project` 是在 `Eask` 文件中聲明的項目名稱。 有關詳細信息,請參閱 [多文件包 (elisp)](https://www.gnu.org/software/emacs/manual/html_node/elisp/Multi_002dfile-Packages.html#Multi_002dfile-Packages)。 ## 🔍 eask generate pkg-file 生成一個 pkg 文件。 將包描述符文件寫入項目根目錄中的 `project-pkg.el`。 別名: `pkg`, `pkg-el` ```sh eask [GLOBAL-OPTIONS] generate pkg-file ``` `project` 是在 `Eask` 文件中聲明的項目名稱。 有關詳細信息,請參閱 [多文件包 (elisp)](https://www.gnu.org/software/emacs/manual/html_node/elisp/Multi_002dfile-Packages.html#Multi_002dfile-Packages)。 ## 🔍 eask generate recipe 生成 recipe 文件。 ```sh eask [GLOBAL-OPTIONS] generate recipe [DESTINATION] ``` 如果未指定 [DESTINATION],則默認導出到 `/recipes` 文件夾。 ## 🔍 eask generate license 生成 LICENSE 文件。 ```sh eask [GLOBAL-OPTIONS] generate license ``` name` 是許可證的類型,請參閱 https://api.github.com/licenses 了解所有選擇。 {{< hint info >}} 💡 此命令使用包 [license-templates](https://github.com/jcs-elpa/license-templates) 生成忽略文件。 {{< /hint >}} ## 🔍 eask generate ignore 生成 `ignore` 文件。 ```sh eask [GLOBAL-OPTIONS] generate ignore ``` {{< hint info >}} 💡 此命令使用包 [gitignore-templates](https://github.com/xuchunyang/gitignore-templates.el) 生成忽略文件。 {{< /hint >}} ## 🔍 eask generate test ert 為 [ert][]測試建立一個新的測試項目。 ```sh eask [GLOBAL-OPTIONS] generate test ert [NAMES..] ``` ## 🔍 eask generate test ert-runner 為 [ert-runner][] 建立一個新的測試項目。 ```sh eask [GLOBAL-OPTIONS] generate test ert-runner [NAMES..] ``` ## 🔍 eask generate test buttercup 為專案建立一個新的 [Buttercup][] 設定。 ```sh eask [GLOBAL-OPTIONS] generate test buttercup ``` ## 🔍 eask generate test ecukes 為專案創建一個新的 [Ecukes][] 設定。 ```sh eask [GLOBAL-OPTIONS] generate test ecukes ``` ## 🔍 eask generate workflow circle-ci 生成 [CircleCI][] 工作流 yaml 文件。 默認文件名為 `config.yml`。 ```sh eask [GLOBAL-OPTIONS] generate workflow circle-ci [--file] ``` 這將在 `.circleci/` 下生成 yaml 文件! ## 🔍 eask generate workflow github 生成 [GitHub Actions][] 工作流 yaml 文件。 默認文件名為 `test.yml`。 ```sh eask [GLOBAL-OPTIONS] generate workflow github [--file] ``` 這將在 `.github/workflow/` 下生成 yaml 文件! ## 🔍 eask generate workflow gitlab 生成 [GitLab Runner][] 工作流程 yaml 文件。 默認文件名為 `.gitlab-ci.yml`。 ```sh eask [GLOBAL-OPTIONS] generate workflow gitlab [--file] ``` ## 🔍 eask generate workflow travis-ci 生成 [Travis CI][] 工作流 yaml 文件。 默認文件名為 `.travis.yml`。 ```sh eask [GLOBAL-OPTIONS] generate workflow travis-ci [--file] ``` # 🚩 連結 此包與本地文件系統的依賴關係之間的鏈接。 鏈接的依賴項避免了從遠程存檔下載依賴項的需要。 鏈接到的包必須有一個 `Eask` 文件或一個 `-pkg.el` 文件。 ## 🔍 eask link add 將給定的 *source* 目錄鏈接到此項目的包目錄,在給定的 *package* 名稱下。 ```sh eask [GLOBAL-OPTIONS] link add ``` ## 🔍 eask link delete 刪除本機連結的套件。 別名: `remove` ```sh eask [GLOBAL-OPTIONS] link delete [NAMES..] ``` ## 🔍 eask link list 列出所有鏈接。 ```sh eask [GLOBAL-OPTIONS] link list ``` # 🚩 清理 刪除建置過程中產生的各種檔案。 ## 🔍 eask clean workspace 從當前 workspace 中刪除 `.eask` 。 別名: `.eask` ```sh eask [GLOBAL-OPTIONS] clean workspace ``` ⛔️ 不要指定選項 `--config, -c`,否則它會刪除你的整個 `~/.emacs.d`。 ```elisp eask clean workspace -g ``` ## 🔍 eask clean elc 刪除所有 `.elc` 文件。 這將尊重您的 `Eask` 文件。 ```sh eask [GLOBAL-OPTIONS] clean elc ``` ## 🔍 eask clean dist 刪除儲存建立輸出的 `dist` 目錄。 別名: `distribution` ```sh eask [GLOBAL-OPTIONS] clean dist ``` ## 🔍 eask clean autoloads 刪除生成的 autoload 文件。 ```sh eask [GLOBAL-OPTIONS] clean autoloads ``` ## 🔍 eask clean pkg-file 刪除生成的 pkg 文件。 ```sh eask [GLOBAL-OPTIONS] clean pkg-file ``` ## 🔍 eask clean log-file 刪除所有生成的日誌文件。 ```sh eask [GLOBAL-OPTIONS] clean log-file ``` ## 🔍 eask clean all 此命令是所有其他清理命令的組合。 - `clean workspace` - `clean elc` - `clean dist` - `clean autoloads` - `clean pkg-file` - `clean log-file` 別名: `everything` ```sh eask [GLOBAL-OPTIONS] clean all ``` # 🚩 检查 對 Emacs 包進行 lint 的命令。 ## 🔍 eask lint package 運行 [package-lint](https://github.com/purcell/package-lint). ```sh eask [GLOBAL-OPTIONS] lint package [FILES..] ``` ## 🔍 eask lint checkdoc 運行 checkdoc (自帶)。 ```sh eask [GLOBAL-OPTIONS] lint checkdoc [FILES..] ``` ## 🔍 eask lint elint 運行 elint (自帶)。 ```sh eask [GLOBAL-OPTIONS] lint elint [FILES..] ``` ## 🔍 eask lint elisp-lint 運行 [elisp-lint](https://github.com/gonewest818/elisp-lint). ```sh eask [GLOBAL-OPTIONS] lint elisp-lint [FILES..] ``` 這確實尊重 .dir-locals.el 文件! 🎉 ## 🔍 eask lint elsa 運行 [elsa](https://github.com/emacs-elsa/Elsa). ```sh eask [GLOBAL-OPTIONS] lint lint elsa [FILES..] ``` ## 🔍 eask lint indent 運行 indent-lint. ```sh eask [GLOBAL-OPTIONS] lint indent [FILES..] ``` ## 🔍 eask lint declare 運行 check-declare (自帶)。 ```sh eask [GLOBAL-OPTIONS] lint declare [FILES..] ``` ## 🔍 eask lint regexps Run [relint](https://github.com/mattiase/relint). 別名: `lint relint` ```sh eask [GLOBAL-OPTIONS] lint regexps [FILES..] ``` ## 🔍 eask lint keywords 運行 keywords checker (自帶). ```sh eask [GLOBAL-OPTIONS] lint keywords ``` ## 🔍 eask lint license 運行 license 檢查器。 ```sh eask [GLOBAL-OPTIONS] lint license ``` ## 🔍 eask lint org 在 Org 檔案上運行 `org-lint`。 ```sh eask [GLOBAL-OPTIONS] lint org [FILES..] ``` # 🚩 測試框架 運行回歸/單元測試。 ## 🔍 eask test activate 激活包; 用於測試包激活 ```sh eask [GLOBAL-OPTIONS] activate [FILES..] ``` {{< hint info >}} 💡 您可以傳入 **[FILES..]** 以便您可以全面測試您的包激活! **[FILES..]** 將在包激活後加載。 {{< /hint >}} ## 🔍 eask test ert 運行 [ert][] 測試。 ```sh eask [GLOBAL-OPTIONS] test ert [FILES..] ``` ## 🔍 eask test ert-runner 使用 [ert-runner][] 運行 [ert][] 測試。 ```sh eask [GLOBAL-OPTIONS] test ert-runner ``` ## 🔍 eask test buttercup 運行 [buttercup][] 測試。 ```sh eask [GLOBAL-OPTIONS] test buttercup ``` ## 🔍 eask test ecukes 運行 [ecukes][] 測試。 ```sh eask [GLOBAL-OPTIONS] test ecukes [FILES..] ``` ## 🔍 eask test melpazoid 運行 [melpazoid][] 測試。 ```sh eask [GLOBAL-OPTIONS] test melpazoid [DIRECTORIES..] ``` {{< hint info >}} 💡 如果未傳入 **[DIRECTORIES..]**,它將使用目前工作空間。 {{< /hint >}} # 🚩 格式化 格式化 Emacs 源文件的命令。 ## 🔍 eask format elisp-autofmt 運行 [elisp-autofmt][] 格式器. ```sh eask [GLOBAL-OPTIONS] format elisp-autofmt [FILES..] ``` ## 🔍 eask format elfmt 運行 [elfmt][] 格式器. ```sh eask [GLOBAL-OPTIONS] format elfmt [FILES..] ``` # 🚩 控制 DSL 控制 DSL 的指令列表。 ## 🔍 eask source add 新增一個包源。 ```sh eask [GLOBAL-OPTIONS] source add [URL] ``` ## 🔍 eask source delete 移除一個包源。 別名: `remove` ```sh eask [GLOBAL-OPTIONS] source delete ``` ## 🔍 eask source list 列出所有包源。 ```sh eask [GLOBAL-OPTIONS] source list ``` {{< hint info >}} 💡 指令與 `eask archives` 相同! {{< /hint >}} # 🚩 實用工具 其他輔助命令。 ## 🔍 eask upgrade-eask 將 Eask 升級到最新版本。 別名: `upgrade-self` ```sh eask [GLOBAL-OPTIONS] upgrade-eask ``` {{< hint warning >}} 💡 這只有在您從源代碼安裝時才有效! {{< /hint >}} ## 🔍 eask locate 顯示 Eask 的安裝位置。 ```sh eask [GLOBAL-OPTIONS] locate ``` ## 🔍 eask root 顯示包的安裝目錄。 ```sh eask [GLOBAL-OPTIONS] root ``` # 🚩 檢查器 檢查您的 Eask 文件的命令。 ## 🔍 eask analyze 檢查 `Eask` 文件。 ```sh eask [GLOBAL-OPTIONS] analyze [FILES..] ``` 例子: ```bash # lint all Eask-files in the current directory and subdirectories eask analyze # lint specific files eask analyze Eask Eask.27 # lint all Eask-files in specified directory and subdirectories eask analyze src/ # print result as JSON eask analyze --json ``` 有關更多詳細信息,請運行 `eask analyze --help`。 # 🚩 全域選項 以下選項適用於所有 Eask 命令: ## 🔍 --global, -g 這將使用 ~/.eask/ 而不是包開發環境。 這用於其他任務。 例如,`cat` 等。 ```sh eask -g [COMMAND] ``` ## 🔍 --config, -c 這將使用 `~/.emacs.d/` 而不是包開發環境。 這用於為您的**Emacs 配置**做一些事情。 例如,包管理等。 ```sh eask -c [COMMAND] ``` ## 🔍 --all, -a 啟用 `all` 標誌。 ```sh eask -a [COMMAND] ``` ## 🔍 --quick, -q 乾淨地啟動而不加載配置文件。 ```sh eask -q [COMMAND] ``` ## 🔍 --force, -f 強制執行命令。 強制卸載包 `dash` ,即使它是另一個包的依賴項 ```sh eask -f [COMMAND] ``` ## 🔍 --debug 啟用調試信息。 這相當於: ```elisp (setq debug-on-error t) ``` ## 🔍 --strict 觸發錯誤代替警告。 例如,在 **eask compile** 中: ```elisp (setq byte-compile-error-on-warn t) ``` ## 🔍 --allow-error 在不終止 Emacs 的情況下繼續執行。 ## 🔍 --insecure 使用 HTTP 而不是 HTTPS 連接存檔。 ## 🔍 --timestamps 啟用/禁用時間戳。 ## 🔍 --log-level 啟用/禁用日誌標頭。 ## 🔍 --log-file, --lf 是否生成日誌文件。 ## 🔍 --no-color 禁用顏色輸出。 ## 🔍 --elapsed-time, --et 顯示每個操作之間經過的時間。 ## 🔍 --verbose, -v `` 將詳細程度從 0 設置為 5。 ```sh eask --verbose 4 [COMMAND] ``` ## 🔍 --version 顯示版本號。 ## 🔍 --help 顯示幫助。 # 🚩 代理選項 ## 🔍 --proxy `` 為 HTTP 和 HTTPS 設置 Emacs 代理: ```sh eask --proxy "localhost:8888" [COMMAND] ``` ## 🔍 --http-proxy `` 僅為 HTTP 設置 Emacs 代理。 ## 🔍 --https-proxy `` 僅為 HTTPS 設置 Emacs 代理。 ## 🔍 --no-proxy `` 不要對任何 URL 匹配模式使用代理。 `` 是 Emacs 正則表達式。 [Cask]: https://github.com/cask/cask [Eldev]: https://emacs-eldev.github.io/eldev/ [Keg]: https://github.com/conao3/keg.el [CircleCI]: https://circleci.com/ [GitHub Actions]: https://github.com/features/actions [GitLab Runner]: https://docs.gitlab.com/runner/ [Travis CI]: https://www.travis-ci.com/ [el2org]: https://github.com/tumashu/el2org [ert]: https://www.gnu.org/software/emacs/manual/html_node/ert/ [ert-runner]: https://github.com/rejeep/ert-runner.el [buttercup]: https://github.com/jorgenschaefer/emacs-buttercup [ecukes]: https://github.com/ecukes/ecukes [melpazoid]: https://github.com/riscy/melpazoid [elisp-autofmt]: https://codeberg.org/ideasman42/emacs-elisp-autofmt [elfmt]: https://github.com/riscy/elfmt ================================================ FILE: docs/content/Getting-Started/Directory-Structure/_index.en.md ================================================ --- title: 🏗️ Directory Structure weight: 350 --- {{< toc >}} Running the **`eask create package`** generator from the command-line will create a directory with the following structure: ```text . ├── .gitignore ├── Makefile ├── Eask ├── README.md └── {package-file}.el ``` ## 📂 Directory Structure Explained The following is a high-level overview of each of the files. `.gitignore` Gitignore file, to ignore files you don't like to include from your repository. By default, it already excludes `files` and `directories` that are generated by Eask. `Makefile` Makefile that has already included basic tests for your package. It now has the following tasks be default: * Test build (packaging + installing) * Test byte-compile * Test checkdoc (style checker) * Test lint (package linter) `README.md` The generated document file. This is used to display the main page from your repository. `{package-file}.el` This is the main package file; where you should write your elisp code. If you attempt to create a multiple-file package; you would need to edit the `Eask`-file accordingly. ================================================ FILE: docs/content/Getting-Started/Directory-Structure/_index.zh-tw.md ================================================ --- title: 🏗️ 目錄結構 weight: 350 --- {{< toc >}} 從命令行運行 **`eask create package`** 生成器將創建 具有以下結構的目錄: ```text . ├── .gitignore ├── Makefile ├── Eask ├── README.md └── {package-file}.el ``` ## 📂 目錄結構說明 以下是每個文件的高級概述。 `.gitignore` Gitignore 文件,忽略您不想從存儲庫中包含的文件。 默認情況下,它已經排除了 Eask 生成的 `文件` 和 `目錄`。 `Makefile` 已經包含包的基本測試的 Makefile。 它現在默認具有以下任務: * 測試構建(打包+安裝) * 測試字節編譯 * 測試 checkdoc(樣式檢查器) * 測試 lint(包 linter) `README.md` 生成的文檔文件。 這用於顯示存儲庫中的主頁。 `{package-file}.el` 這是主要的包文件; 你應該在哪裡寫你的elisp代碼。 如果您嘗試創建多文件包; 您需要相應地編輯 `Eask` 文件。 ================================================ FILE: docs/content/Getting-Started/Finding-Emacs/_index.en.md ================================================ --- title: 🔭 Finding Emacs weight: 150 --- By default, packages are installed for the default Emacs, i.e., the one behind the `emacs` command. To pick a different Emacs, set the environment variable `EMACS` to the command name or executable path of the Emacs to use: ```sh EMACS="emacs-26.1" eask command ``` Note that installed dependencies are scoped on the version of Emacs. So when switching between versions you will have to install the dependencies for each: ```sh EMACS="emacs-26.3" eask install ``` There are, unfortunately, circumstances under which Emacs itself resets the `EMACS` variable in a way which conflicts with **eask**, in which case you can use the environment variable `EASK_EMACS` instead. Specifically, this problem effects: Emacs-26, for `M-x compile`, `M-x shell` or `M-x term`, for Emacs-27 and Emacs-28 only for `M-x term`. ================================================ FILE: docs/content/Getting-Started/Finding-Emacs/_index.zh-tw.md ================================================ --- title: 🔭 尋找 Emacs weight: 150 --- 默認情況下,會為默認的 Emacs 安裝軟件包,即 `emacs` 命令後面的軟件包。 要選擇不同的 Emacs,請將環境變量 `EMACS` 設置為要使用的 `Emacs` 的命令名稱或可執行路徑: ```sh EMACS="emacs-26.1" eask command ``` 請注意,已安裝的依賴項受 Emacs 版本的影響。 因此,在版本之間切換時,您必須為每個版本安裝依賴項: ```sh EMACS="emacs-26.3" eask install ``` 不幸的是,在某些情況下,Emacs 本身會以與 **eask** 衝突的方式重置 `EMACS` 變量,在這種情況下, 您可以改用環境變量 `EASK_EMACS`。 具體來說,此問題會影響:Emacs-26,對於 `M-x compile`、 `M-x shell` 或 `M-x term`,對於 Emacs-27 和 Emacs-28 僅適用於 `M-x term`。 ================================================ FILE: docs/content/Getting-Started/Install-Eask/_index.en.md ================================================ --- title: 💾 Install Eask weight: 200 --- This document guides you through the installation of Eask. Install Eask on macOS, Linux, Windows, BSD, and on any machine that can run the [Node.js][]. {{< toc >}} ## 💾 Prebuilt binaries Download the appropriate version for your platform from [Eask Releases](https://github.com/emacs-eask/cli/releases). Once downloaded, the binary can be run from anywhere. You don’t need to install it in a global location. This works well for shared hosts and other systems where you don’t have a privileged account. Ideally, you should install it somewhere in your `PATH` for easy use. `/usr/local/bin` is the most probable location. ## 💾 Using Shell On macOS or Linux: ```sh curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh ``` On Windows: ```sh curl.exe -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.bat | cmd /Q ``` ## 💾 Package managers ### 📦 npm (Cross-platform) If you have [npm][] installed on your machine, you can install Eask with the following one-liner: ```sh npm install -g @emacs-eask/cli ``` ### 📦 Nix (macOS or Linux) [Nix][] is a free and open-source package manager for macOS and Linux. To install the Eask CLI: ```sh nix profile install nixpkgs#eask-cli ``` ### 📦 Homebrew (macOS or Linux) [Homebrew][] is a free and open-source package manager for macOS and Linux. To install the Eask CLI: ```sh brew install eask-cli ``` Or using the tap provided by us: ```sh brew tap emacs-eask/cli https://github.com/emacs-eask/packaging brew install eask-cli ``` ### 📦 MacPorts (macOS) [MacPorts][] is a free and open-source package manager for macOS. To install the Eask CLI: ```sh sudo port install eask-cli ``` ### 📦 Debian (Linux) Derivatives of the [Debian][] distribution of Linux include [elementary OS][], [KDE neon][], [Linux Lite][], [Linux Mint][], [MX Linux][], [Pop!_OS][], [Ubuntu][], [Zorin OS][], and others. ```sh sudo curl -SsL -o /etc/apt/trusted.gpg.d/easksource.gpg https://raw.githubusercontent.com/emacs-eask/packaging/master/debian/KEY.gpg sudo curl -SsL -o /etc/apt/sources.list.d/easksource.list https://raw.githubusercontent.com/emacs-eask/packaging/master/debian/easksource.list sudo apt update --allow-insecure-repositories sudo apt install eask-cli --allow-unauthenticated ``` You can also download Debian packages from the [packaging][packaging/debian] repo. ### 📦 Fedora (Linux) [Fedora][] 44 onwards includes a [package](https://packages.fedoraproject.org/pkgs/eask/eask/) for the Eask CLI. ```sh sudo dnf install eask ``` ### 📦 Snap (Linux) [Snap][] is a free and open-source package manager for Linux. Available for most distributions, snap packages are simple to install and are automatically updated. ```sh sudo snap install eask-cli ``` ### 📦 Arch (Linux) There's a `PKGBUILD` that builds `eask` from sources and creates a package, so inside the top directory of the repository you can simply run: ```sh makepkg -i ``` ### 📦 Chocolatey (Windows) If you have [Chocolatey][] installed on your machine, you can install Eask with the following one-liner: ```sh choco install eask-cli ``` ### 📦 Scoop (Windows) [Scoop][] is a free and open-source package manager for Windows. To install the Eask CLI: ```sh scoop bucket add extras scoop install eask-cli ``` Alternatively, you can use our bucket to access the latest release. ```sh scoop bucket add emacs-eask/cli https://github.com/emacs-eask/packaging scoop install eask-cli ``` ### 📦 Winget (Windows) [Winget][] is Microsoft’s official free and open-source package manager for Windows. To install the Eask CLI: ```sh winget install eask.cli ``` ## 💾 Build from source ### 🚩 Prerequisite Tools - [Git][] - [Node.js][] - [npm][] Alternatively, you can clone it directly from this repo ```sh # clone the repo git clone https://github.com/emacs-eask/cli eask-cli # change the working directory to eask-cli cd eask-cli # install the requirements npm install ``` ### 🏡 Setup (through script) You can now run `eask` using the script `bin/eask`; add `/path/to/eask-cli/bin/` to your environment `PATH` to execute eask from any location! On Linux/macOS, ```sh export PATH="path/to/eask-cli/bin:$PATH" ``` On Windows, ```batch set PATH=%PATH%;c:/path/to/eask-cli/bin ``` Once you have set it up correctly, try `eask --version` then you should see the current `eask`'s version number! 🎉 🎊 ### 🏡 Setup (through executable) To run `eask` through executable, you will need [pkg][] installed on your machine. ```sh # install it locally in the workspace scope npm install --dev # or # install it globally npm install -g pkg ``` Subsequently, run the following command to build the executable. By default, it will generate an executable in the `dist` folder. ```sh # build from sources. For available targets see `scripts` in `package.json` npm run pkg-linux-x64 # move `lisp` to `dist` folder mv lisp dist ``` You can now run `eask` using the executable `dist/eask`; add `/path/to/eask-cli/dist/` to your environment `PATH` to execute eask from any location! 🎉 🎊 [packaging/debian]: https://github.com/emacs-eask/packaging/tree/master/debian [Nix]: https://nixos.org/ [Homebrew]: https://brew.sh/ [MacPorts]: https://www.macports.org/ [Snap]: https://snapcraft.io/ [Chocolatey]: https://chocolatey.org/ [Scoop]: https://scoop.sh/ [Winget]: https://learn.microsoft.com/en-us/windows/package-manager/ [Git]: https://git-scm.com/ [Node.js]: https://nodejs.org/en/ [npm]: https://www.npmjs.com/ [pkg]: https://github.com/vercel/pkg [Debian]: https://www.debian.org/ [elementary OS]: https://elementary.io/ [Fedora]: https://fedoraproject.org/ [KDE neon]: https://neon.kde.org/ [Linux Lite]: https://www.linuxliteos.com/ [Linux Mint]: https://linuxmint.com/ [MX Linux]: https://mxlinux.org/ [Pop!_OS]: https://pop.system76.com/ [Ubuntu]: https://ubuntu.com/ [Zorin OS]: https://zorin.com/os/ ================================================ FILE: docs/content/Getting-Started/Install-Eask/_index.zh-tw.md ================================================ --- title: 💾 安裝 Eask weight: 200 --- 本文檔將指導您完成 Eask 的安裝。 安裝 Eask 在 macOS、Linux、Windows、BSD 以及任何可以執行 [Node.js][]。 {{< toc >}} ## 💾 預建置檔案 從 [Eask Releases](https://github.com/emacs-eask/cli/releases) 下載適合您平台的版本。 下載後,二進製文件可以從任何地方運行。 您無需將其安裝在全球位置。 這適用於您沒有特權帳戶的共享主機和其他系統。 理想情況下,您應該將它安裝在 `PATH` 中的某個位置以便於使用。 `/usr/local/bin` 是最有可能的位置。 ## 💾 使用終端 在 macOS 或 Linux: ```sh curl -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.sh | sh ``` 在 Windows: ```sh curl.exe -fsSL https://raw.githubusercontent.com/emacs-eask/cli/master/webinstall/install.bat | cmd /Q ``` ## 💾 包管理器 ### 📦 npm (跨平台) 如果您的機器上安裝了 [npm][],您可以使用以下一行代碼安裝 Eask: ```sh npm install -g @emacs-eask/cli ``` ### 📦 Nix (macOS 或 Linux) [Nix][] 是一個適用於 macOS 和 Linux 的免費開源套件管理器。 若要安裝 Eask CLI,請執行下列操作: ```sh nix profile install nixpkgs#eask-cli ``` ### 📦 Homebrew (macOS 或 Linux) [Homebrew][] 是一個適用於 macOS 和 Linux 的免費開源套件管理器。 若要安裝 Eask CLI,請執行下列操作: ```sh brew install eask-cli ``` 或使用我們提供的 tap: ```sh brew tap emacs-eask/cli https://github.com/emacs-eask/packaging brew install eask-cli ``` ### 📦 MacPorts (macOS) [MacPorts][] 是一款適用於 macOS 的免費開源套件管理器。 若要安裝 Eask CLI,請執行下列操作: ```sh sudo port install eask-cli ``` ### 📦 Debian (Linux) Linux [Debian][] 發行版的衍生版本包括 [elementary OS][]、[KDE neon][]、 [Linux Lite][]、[Linux Mint][]、[MX Linux][]、[Pop!_OS][]、[Ubuntu][]、 [Zorin OS][] 等。 ```sh sudo curl -SsL -o /etc/apt/trusted.gpg.d/easksource.gpg https://raw.githubusercontent.com/emacs-eask/packaging/master/debian/KEY.gpg sudo curl -SsL -o /etc/apt/sources.list.d/easksource.list https://raw.githubusercontent.com/emacs-eask/packaging/master/debian/easksource.list sudo apt update --allow-insecure-repositories sudo apt install eask-cli --allow-unauthenticated ``` 您也可以直接從 [packaging][packaging/debian] 代碼庫下載 Debian 軟體包。 ### 📦 Fedora (Linux) [Fedora][] 44 及後續版本包含一個 [套件](https://packages.fedoraproject.org/pkgs/eask/eask/) 用於 Eask 命令列介面。 ```sh sudo dnf install eask ``` ### 📦 Snap (Linux) [Snap][] 是一款適用於 Linux 的免費開源套件管理器。 snap 套件適用於大多數發行版,安裝簡單且會自動更新。 ```sh sudo snap install eask-cli ``` ### 📦 Arch (Linux) 有一個 `PKGBUILD` 可以從原始程式碼建立 `eask` 並建立一個包,因此在儲存庫的最上層目錄中您可以簡單地運行: ```sh makepkg -i ``` ### 📦 Chocolatey (Windows) 如果您的計算機上安裝了 [Chocolatey][],則可以使用以下一行代碼安裝 Eask: ```sh choco install eask-cli ``` ### 📦 Scoop (Windows) [Scoop][] 是一個適用於 Windows 的免費開源套件管理器。 若要安裝 Eask CLI,請執行下列操作: ```sh scoop bucket add extras scoop install eask-cli ``` 另外,您也可以使用我們的 bucket 來獲得最新版本。 ```sh scoop bucket add emacs-eask/cli https://github.com/emacs-eask/packaging scoop install eask-cli ``` ### 📦 Winget (Windows) [Winget][]是微軟官方的 Windows 免費開源軟體套件管理器。 若要安裝 Eask CLI,請執行下列操作: ```sh winget install eask.cli ``` ## 💾 從原始碼構建 ### 🚩 前置工具 - [Git][] - [Node.js][] - [npm][] 或者,您可以直接從這個代碼庫克隆它: ```sh # 克隆這個代碼庫 git clone https://github.com/emacs-eask/cli eask-cli # 將工作目錄更改為 eask-cli cd eask-cli # 安裝所有依賴 npm install ``` ```sh # 從源頭建構; 有關可用目標,請參閱 `package.json` 中的 `scripts` npm run pkg-linux-x64 ``` ### 🏡 設定(透過腳本) 確保根據您的系統設置環境路徑變量: 在 Linux/macOS 上, ```sh export PATH="path/to/eask-cli/bin:$PATH" ``` 在 Windows 上, ```batch set PATH=%PATH%;c:/path/to/eask-cli/bin ``` 正確設置後,嘗試 `eask --version` 然後您應該會看到當前 `eask` 的版本號! 🎉🎊 ### 🏡 設定(透過可執行檔) To run `eask` through executable, you will need [pkg][] installed on your machine. ```sh # 區域安裝 npm install --dev # 或 # 全域安裝 npm install -g pkg ``` 隨後,執行以下命令產生可執行檔。 預設情況下,它會在 `dist` 資料夾中產生一個可執行檔。 ```sh # 從原始碼建置。有關可用目標,請參閱 `package.json` 中的 `scripts` 。 npm run pkg-linux-x64 # 將 `lisp` 移至 `dist` 資料夾 mv lisp dist ``` 現在,您可以使用可執行檔 `dist/eask` 執行 `eask`;在環境 `PATH` 中新增 `/path/to/eask-cli/dist/`,以便從任何位置執行 `eask`!🎉🎊 [packaging/debian]: https://github.com/emacs-eask/packaging/tree/master/debian [Nix]: https://nixos.org/ [Homebrew]: https://brew.sh/ [MacPorts]: https://www.macports.org/ [Snap]: https://snapcraft.io/ [Chocolatey]: https://chocolatey.org/ [Scoop]: https://scoop.sh/ [Winget]: https://learn.microsoft.com/en-us/windows/package-manager/ [Git]: https://git-scm.com/ [Node.js]: https://nodejs.org/en/ [npm]: https://www.npmjs.com/ [pkg]: https://github.com/vercel/pkg [Debian]: https://www.debian.org/ [elementary OS]: https://elementary.io/ [Fedora]: https://fedoraproject.org/ [KDE neon]: https://neon.kde.org/ [Linux Lite]: https://www.linuxliteos.com/ [Linux Mint]: https://linuxmint.com/ [MX Linux]: https://mxlinux.org/ [Pop!_OS]: https://pop.system76.com/ [Ubuntu]: https://ubuntu.com/ [Zorin OS]: https://zorin.com/os/ ================================================ FILE: docs/content/Getting-Started/Introduction/_index.en.md ================================================ --- title: 🚪 Introduction weight: 0 --- Eask was originally designed as a package development tool for Elisp projects. However, it has since expanded to support a wide range of Emacs Lisp tasks. It can now be used in three major ways: 1. As a development tool for Elisp packages. 2. For managing dependencies in your Emacs configuration. 3. To run Elisp programs for a variety of purposes (essentially functioning as a runtime). With these capabilities in mind, what sets Eask apart from other build tools like [Cask][], [makem.sh][], and [Eldev][]? Great question! Eask has evolved beyond just a build tool—it serves multiple purposes! Here’s what Eask aims to be: - **Consistent**: Provides a reliable sandboxing environment across all systems. - **Versatile**: Includes commonly used Emacs commands like `byte-compilation`, `checkdoc`, and more. - **Robust**: Delivers useful results even when user errors occur. - **Lightweight**: Runs on any platform without dependencies. *📝 P.S. See [Why Eask?](https://emacs-eask.github.io/Getting-Started/Introduction/#-why-eask) for more detailed information.* ## ❓ Why Eask? `Eask` follows the same philosophy as [Cask][]. To understand why you should use `Eask` (or [Cask][]), check out the [Why Cask?](https://cask.readthedocs.io/en/latest/guide/introduction.html#introduction-why-cask) section on their website. Many tools, such as [Cask][], [makem.sh][], and [Eldev][], don’t fully support Windows. `Cask` has dropped support for Legacy Windows, `makem.sh` relies on Bash, and while `Eldev` does support Windows, its author doesn’t actively use it on the platform, meaning it lacks full testing (as seen in their CI workflows). In contrast, `Eask` is designed to work across all major platforms, including Linux, macOS, and Windows It prioritizes cross-platform compatibility and ensures consistency across different operating systems. If `Eask` runs on your machine, it will work reliably on any platform. Here’s our recommendation: if you’re developing an OS-specific package that will never need cross-platform support, other tools may be a better fit. However, if you want a tool that ensures seamless consistency across different operating systems, Eask is an excellent choice. Another major advantage of `Eask` is its transparency—there are no hidden workflows or obscure processes running in the background. Additionally, `Eask` strictly avoids hacks or workaround fixes, ensuring that solutions are clean, maintainable, and aligned with best practices. ## ⚖️ Comparisons The table was compiled by reading these projects’ documentation and source code, but the author is not an expert on these tools. Corrections are welcome. ### 🔍 Project Wise The table shows what technology has been chosen by their author and how the project is being constructed. Furthermore, what technical decisions have they made? Drop support? Project's layout? Etc. | | Eask | Cask | Eldev | makem.sh | |----------------|-------------------|----------------------------|----------------|----------------------------| | bin folder | binary, bash, bat | bash, bat | bash, bat, ps1 | bash | | Cross-Platform | ✅ | ❌, no [Windows][] support | ✅ | ❌, no [Windows][] support | | Emacs version | 26.1+ | 24.5+ | 24.4+ | 26.1+ | | Size | 9,000+ lines | 3,000+ lines | 8,000+ lines | 1,200+ lines | | Executable | ✅ | ❌ | ❌ | ❌ | | Pure Elisp | ❌, JavaScript | ✅ | ✅ | ✅ | | CLI Parser | [yargs][] | [commander][] | built-in | built-in | {{< hint info >}} 💡 **makem.sh** has a good comparisons document as well, visit their [site](https://github.com/alphapapa/makem.sh#comparisons) {{< /hint >}} ### 🔍 Feature Wise This is the feature comparison between each tool. Every tool has its advantages; choose the right tool that works for you! If the features are not listed below, either it is forgotten or simply considered too essential, so every tool has it; hence we don't add them to the list. | | Eask | Cask | Eldev | makem.sh | |---------------------------|-----------------------------------------|--------------------------|----------------|----------| | Elisp configuration | ✅, [DSL][DSL-Eask] is optional | ❌, [DSL][DSL-Cask] only | ✅, pure elisp | ❌ | | Handle `archives` failure | ✅, see [archives][emacs-eask/archives] | ❌ | ❌ | ❌ | | `create` project, etc | ✅ | ❌ | ❌ | ❌ | | `link` local dependencies | ✅ | ✅ | ✅ | ❌ | | `exec` program | ✅ | ✅ | ❌ | ❌ | | `eval` expressions | ✅ | ✅ | ✅ | ❌ | | `emacs` execution | ✅ | ✅ | ❌ | ❌ | | Support `docker` | ✅ | ❌ | ✅ | ❌ | | Built-in `linters` | ✅ | ❌ | ✅ | ❌ | | Built-in `tests` | ✅ | ❌ | ✅ | ❌ | | Run script | ✅ | ❌ | ❌ | ❌ | | Self-defined commands | ✅ | ❌ | ✅ | ❌ | | Subcommand | ✅ | ❌ | ❌ | ❌ | ## 📰 News - `0.12.x` - Correctly handle the exit code. - `0.11.x` - Add commands `install-file` and `install-vc`. - `0.10.x` - Add five new commands and improve the default user experience. - `0.9.x` - Enhance overall user experience. - `0.8.x` - Add `link` command. - `0.7.x` - Fix `default-directory` isn't honored by **-g** option. - `0.6.x` - You can now use `eask create` to create an Elisp project. - `0.5.x` - Handle error for failed archive. - `0.4.x` - Add color logger. - `0.3.x` - Add verbosity level and timestamps. ## 📝 Todo list ### 🔍 Core commands - [ ] [FEAT] Add `publish` command; to publish the package to the eask archive? ### 🔍 Eask-file commands - N/A ## 📂 Underlying Projects The design of Eask was greatly influenced by the following projects: - [Cask][] - Project management tool for Emacs - [makem.sh][] - Makefile-like script for building and testing Emacs Lisp packages - [epm][] - Emacs Package Manager - [Eldev][] - Elisp Development Tool [emacs-eask/archives]: https://github.com/emacs-eask/archives [Cask]: https://github.com/cask/cask [makem.sh]: https://github.com/alphapapa/makem.sh [Eldev]: https://github.com/doublep/eldev [epm]: https://github.com/xuchunyang/epm [yargs]: https://github.com/yargs/yargs [commander]: https://github.com/rejeep/commander.el [DSL-Eask]: https://emacs-eask.github.io/DSL/ [DSL-Cask]: https://cask.readthedocs.io/en/latest/guide/dsl.html [Windows]: https://www.microsoft.com/en-us/windows?r=1 ================================================ FILE: docs/content/Getting-Started/Introduction/_index.zh-tw.md ================================================ --- title: 🚪 介紹 weight: 0 --- Eask 原本是為 Elisp 專案設計的套件開發工具。然而,它後來擴展到支援廣泛的 Emacs Lisp 任務。 現在它可以用在三個主要方面: 1. 作為 Elisp 套件的開發工具。 2. 用於管理 Emacs 設定中的相依性。 3. 運行 Elisp 程式以達到各種目的 (本質上作為運行時)。 考慮到這些功能,Eask 與 [Cask][]、[makem.sh][] 和 [Eldev][] 等其他編譯工具有何不同? 很好的問題!Eask 已經不僅僅是一個編譯工具,它還具有多種用途!以下是 Eask 的目標: - **一致性**: 在所有系統中提供可靠的沙箱環境。 - **多功能**: 包含常用的 Emacs 指令,如 `byte-compilation`、`checkdoc` 等。 - **可靠**: 即使發生使用者錯誤,也能提供有用的結果。 - **輕量級**: 可在任何平台上執行,無須依賴任何平台。 *📝 P.S. 詳細資訊請參閱 [為什麼選擇 Eask?](https://emacs-eask.github.io/zh-tw/Getting-Started/Introduction/#-%e7%82%ba%e4%bb%80%e9%ba%bc%e9%81%b8%e6%93%87-eask)。* ## ❓ 為什麼選擇 Eask? `Eask` 遵循與 [Cask][] 相同的理念。若要瞭解為何要使用 `Eask` (或 [Cask][]),請查看其網站上的 [Why Cask?](https://cask.readthedocs.io/en/latest/guide/introduction.html#introduction-why-cask)。 許多工具,例如 [Cask][]、[makem.sh][] 和 [Eldev][],並不完全支援 Windows。 `Cask` 已經放棄對傳統 Windows 的支援,`makem.sh` 則依賴 Bash,而 Eldev 雖然支援 Windows, 但其作者並未在該平台上積極使用,這意味著它缺乏完整的測試(從其 CI 工作流程中可以看出)。 相較之下,`Eask` 的設計可在所有主要平台上運作,包括 Linux、macOS 和 Windows 它優先處理跨平台相容性, 並確保不同作業系統之間的一致性。如果 `Eask` 在您的機器上運行,它將可靠地在任何平台上運行。 以下是我們的建議:如果您要開發一個永遠不需要跨平台支援的特定作業系統套件,其他工具可能更適合 。但是,如果您想要一個能夠確保跨不同作業系統的無縫一致性的工具,`Eask` 是一個絕佳的選擇。 `Eask` 的另一大優勢是它的透明性--沒有隱藏的工作流程,也沒有在背景中運行的隱晦進程。 此外,`Eask` 嚴格避免駭客或變通修正,確保解決方案乾淨、可維護且符合最佳實務。 ## ⚖️ 比較 該表是通過閱讀這些項目的文檔和源代碼編制的,但作者不是這些工具的專家。 歡迎指正。 ### 🔍 專案方面 該表顯示了作者選擇的技術以及項目的構建方式。 此外,他們做出了哪些技術決策? 放棄支持? 項目佈局? 等等。 | | Eask | Cask | Eldev | makem.sh | |------------|-------------------|------------------------|----------------|------------------------| | bin 資料夾 | binary, bash, bat | bash, bat | bash, bat, ps1 | bash | | 跨平台 | ✅ | ❌, 不支援 [Windows][] | ✅ | ❌, 不支援 [Windows][] | | Emacs 版本 | 26.1+ | 24.5+ | 24.4+ | 26.1+ | | 檔案大小 | 9,000+ 行 | 3,000+ 行 | 8,000+ 行 | 1,200+ 行 | | 執行檔 | ✅ | ❌ | ❌ | ❌ | | 純 Elisp | ❌, JavaScript | ✅ | ✅ | ✅ | | CLI 解析器 | [yargs][] | [commander][] | 內建 | 內建 | {{< hint info >}} 💡 **makem.sh** 也有很好的比較文檔,請訪問他們的[站點](https://github.com/alphapapa/makem.sh#comparisons) {{< /hint >}} ### 🔍 功能方面 這是每個工具之間的功能比較。 每種工具都有其優點; 選擇適合您的工具! 如果這些功能沒有在下面列出,要么被遺忘,要么只是被認為太重要了,所以每個工具都有它; 因此我們不將它們添加到列表中。 | | Eask | Cask | Eldev | makem.sh | |-------------------------|----------------------------------------|------------------------|--------------|----------| | Elisp 配置 | ✅, [DSL][DSL-Eask] 是可選的 | ❌, 僅 [DSL][DSL-Cask] | ✅, 純 elisp | ❌ | | 處理 `archives` 錯誤 | ✅, 看 [archives][emacs-eask/archives] | ❌ | ❌ | ❌ | | `create` 建立專案, 等等 | ✅ | ❌ | ❌ | ❌ | | `link` 本地依賴 | ✅ | ✅ | ✅ | ❌ | | `exec` 執行軟件 | ✅ | ✅ | ❌ | ❌ | | `eval` 表達式 | ✅ | ✅ | ✅ | ❌ | | `emacs` 執行 | ✅ | ✅ | ❌ | ❌ | | 支援 `docker` | ✅ | ❌ | ✅ | ❌ | | 內建 `linters` | ✅ | ❌ | ✅ | ❌ | | 內建 `tests` | ✅ | ❌ | ✅ | ❌ | | 執行 `script` | ✅ | ❌ | ❌ | ❌ | | 可自行建立指令 | ✅ | ❌ | ✅ | ❌ | | 子指令 | ✅ | ❌ | ❌ | ❌ | ## 📰 消息 請參考[這](https://emacs-eask.github.io/Getting-Started/Introduction/#-news). ## 📝 TODO 事項列表 請參考[這](https://emacs-eask.github.io/Getting-Started/Introduction/#-todo-list). ## 📂 基礎項目 Eask 的設計深受以下項目的影響: - [Cask][] - Emacs 的項目管理工具 - [makem.sh][] -用於構建和測試 Emacs Lisp 包的類似 Makefile 的腳本 - [epm][] - Emacs 包管理器 - [Eldev][] - Elisp 開發工具 [emacs-eask/archives]: https://github.com/emacs-eask/archives [Cask]: https://github.com/cask/cask [makem.sh]: https://github.com/alphapapa/makem.sh [Eldev]: https://github.com/doublep/eldev [epm]: https://github.com/xuchunyang/epm [yargs]: https://github.com/yargs/yargs [commander]: https://github.com/rejeep/commander.el [DSL-Eask]: https://emacs-eask.github.io/DSL/ [DSL-Cask]: https://cask.readthedocs.io/en/latest/guide/dsl.html [Windows]: https://www.microsoft.com/en-us/windows?r=1 ================================================ FILE: docs/content/Getting-Started/Quick-Start/_index.en.md ================================================ --- title: 🔰 Quick Start weight: 100 --- {{< toc >}} Using Eask as your Emacs package management tool. {{< hint info >}} The installation is cross-platform and uses [npm](https://www.npmjs.com/). For instructions on how to install Eask using other methods, see the [install](https://emacs-eask.github.io/Getting-Started/Install-Eask/) section. It is required to have [Git installed](https://git-scm.com/downloads) to run this tutorial. {{< /hint >}} ## 🔍 Step 1: Setup NodeJS runtime and `npm` Please check out their official site [here](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#using-a-node-installer-to-install-nodejs-and-npm) and install `NodeJS` and `npm` corresponds to your current operating system {{< hint ok >}} 💡 If you don't prefer **NodeJS** and **npm**, you can install with [binary](https://emacs-eask.github.io/Getting-Started/Install-Eask/#binary-cross-platform) from our [release](https://github.com/emacs-eask/cli/releases) page. {{< /hint >}} ## 🔍 Step 2: Install Eask ```sh npm install -g @emacs-eask/cli ``` To verify your new installation: ```sh eask --version ``` ## 🔍 Step 3: Navigate to an existing project or create a new project If you already have an existing elisp project, navigate to the project root folder. ```sh cd /path/to/project/dir/ ``` To create one: ```sh eask create package ``` It should create a folder named `` in your current working directory. ## 🔍 Step 4: Create `Eask`-file Skip this step if you chose to create the project with **`eask create`**! Otherwise, to create Eask-file in the existing project: ```sh eask init ``` You will be asked some questions about the package you are going to create: ``` package name: (your-project) version: (1.0.0) description: Your project description! entry point: (your-project.el) emacs version: (26.1) website: https://example.com/project-url/ keywords: tools example About to write to /path/to/project/Eask: ;; -*- mode: eask; lexical-binding: t -*- (package "your-project" "1.0.0" "Your project description!") (website-url "https://example.com/project-url/") (keywords "tools" "example") (package-file "your-project.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") Is this OK? (yes) yes ⏎ ``` You should be able to see an `Eask` file in your project folder. 🎉🎊 ## 🔍 Step 5: Start the package development To check your package information, run: ```sh eask info ``` You should be able to see the following information: ``` your-package (1.0.0) | deps: 0 | devDeps: 0 Your project description! https://example.com/project-url/ keywords: tools, example entry: your-package-file.el kind: single dist .total-files: 0 .unpacked-size: 0 ``` From the start, you would not have any `dependencies` and `devDependencies` (`0` by default)! ## 🔍 Step 6: Manage package archives You can manage package archives by using the `source` directive in your **Eask**-file. ```elisp (source "gnu") ; default (source "melpa") ; Add package archives ``` {{< hint info >}} 💡 See [DSL/source](https://emacs-eask.github.io/DSL/#-source-alias) for more information! {{< /hint >}} ## 🔍 Step 7: Add some dependencies You can add dependencies by using `depends-on` directive in your **Eask**-file. ```elisp ... (depends-on "f") (depends-on "ht") ``` {{< hint danger >}} 💡 Make sure the dependencies you add are available in the package archives! Or else you would get an error **`package-name-' is unavailable**! {{< /hint >}} ## 🔍 Step 8: Install dependencies Now we can install the dependencies we have specified in the **Eask**-file: ```sh eask install-deps ``` You should see Eask executed correctly with the similar output below: ``` Loading package information... done ✓ Installing 2 package dependencies... - [1/2] Installing f (20241003.1131)... done ✓ - [2/2] Installing ht (20230703.558)... done ✓ (Total of 2 dependencies installed, 0 skipped) ``` ## 🔗 See Also - [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/) - [Domain Specific Language](https://emacs-eask.github.io/DSL/) - [Basic Usage](https://emacs-eask.github.io/Getting-Started/Basic-Usage/) ================================================ FILE: docs/content/Getting-Started/Quick-Start/_index.zh-tw.md ================================================ --- title: 🔰 快速開始 weight: 100 --- {{< toc >}} 使用 Eask 作為您的 Emacs 包管理工具。 {{< hint info >}} 安裝是跨平台的,並使用 [npm](https://www.npmjs.com/)。 有關使用其他方法安裝 Eask 的說明,請參閱 [安裝](https://emacs-eask.github.io/Getting-Started/Install-Eask/)部分。 需要安裝 [Git](https://git-scm.com/downloads) 才能運行本教程。 {{< /hint >}} ## 🔍 步驟 1: 設置 NodeJS runtime 和 `npm` 請在 [此處](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#using-a-node-installer-to-install-nodejs-and- npm) 並安裝 `NodeJS` 和 `npm` 對應你當前的操作系統 {{< hint ok >}} 💡 如果您不喜歡 **NodeJS** 和 **npm**,您可以使用 [binary](https://emacs-eask.github.io/Getting-Started/Install-Eask/#binary-cross -platform) 來自我們的 [release](https://github.com/emacs-eask/cli/releases) 頁面。 {{< /hint >}} ## 🔍 步驟 2: 安裝 Eask ```sh npm install -g @emacs-eask/cli ``` 驗證您的新安裝: ```sh eask --version ``` ## 🔍 步驟 3: 導航到現有項目或創建新項目 如果您已有一個現有的 elisp 項目,請導航到項目根文件夾。 ```sh cd /path/to/project/dir/ ``` 創建一個: ```sh eask create package ``` 它應該在您當前的工作目錄中創建一個名為 `` 的文件夾。 ## 🔍 步驟 4: 創建 `Eask` 文件 如果您選擇使用 **`eask create`** 創建項目,請跳過此步驟! 否則,在現有項目中創建 Eask 文件: ```sh eask init ``` 您將被問到一些關於您將要創建的包的問題: ``` package name: (your-project) version: (1.0.0) description: Your project description! entry point: (your-project.el) emacs version: (26.1) website: https://example.com/project-url/ keywords: tools example About to write to /path/to/project/Eask: ;; -*- mode: eask; lexical-binding: t -*- (package "your-project" "1.0.0" "Your project description!") (website-url "https://example.com/project-url/") (keywords "tools" "example") (package-file "your-project.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") Is this OK? (yes) yes ⏎ ``` 您應該能夠在項目文件夾中看到一個 `Eask` 文件。 🎉🎊 ## 🔍 步驟 5: 開始包開發 要檢查您的包裹信息,請運行: ```sh eask info ``` 您應該能夠看到以下信息: ``` your-package (1.0.0) | deps: 0 | devDeps: 0 Your project description! https://example.com/project-url/ keywords: tools, example entry: your-package-file.el kind: single dist .total-files: 0 .unpacked-size: 0 ``` 從一開始,您就不會有任何 `dependencies` 和 `devDependencies`(默認為 `0`)! ## 🔍 步驟 6: 管理包檔案 您可以使用 **Eask** 文件中的 `source` 指令來管理包存檔。 ```elisp (source "gnu") ; 默認 (source "melpa") ; 添加包 archive ``` {{< hint info >}} 💡 有關更多信息,請參閱 [DSL/source](https://emacs-eask.github.io/DSL/#-source-alias)! {{< /hint >}} ## 🔍 步驟 7: 添加一些依賴 您可以在 **Eask** 文件中使用 `depends-on` 指令添加依賴項。 ```elisp ... (depends-on "f") (depends-on "ht") ``` {{< hint danger >}} 💡 確保您添加的依賴項在包存檔中可用! 否則你會得到一個錯誤 **`package-name-' is unavailable**! {{< /hint >}} ## 🔍 步驟 8: 安裝依賴 現在我們可以安裝我們在 **Eask** 文件中指定的依賴項: ```sh eask install-deps ``` 您應該會看到 Eask 正確執行,輸出類似如下: ``` Loading package information... done ✓ Installing 2 package dependencies... - [1/2] Installing f (20241003.1131)... done ✓ - [2/2] Installing ht (20230703.558)... done ✓ (Total of 2 dependencies installed, 0 skipped) ``` ## 🔗 也可以看看 - [Commands and options](https://emacs-eask.github.io/Getting-Started/Commands-and-options/) - [Domain Specific Language](https://emacs-eask.github.io/DSL/) - [Basic Usage](https://emacs-eask.github.io/Getting-Started/Basic-Usage/) ================================================ FILE: docs/content/Getting-Started/_index.en.md ================================================ --- title: Getting Started weight: 100 --- ================================================ FILE: docs/content/Getting-Started/_index.zh-tw.md ================================================ --- title: 開始使用 weight: 100 --- ================================================ FILE: docs/content/License/_index.en.md ================================================ --- title: GNU General Public License weight: 1000 --- Licensed under GPLv3. See below for details. ``` GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ``` ================================================ FILE: docs/content/License/_index.zh-tw.md ================================================ --- title: GNU 通用公共授權條款 weight: 1000 --- 在 GPLv3 下獲得許可。詳情見下文。 ``` GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ``` ================================================ FILE: docs/content/Troubleshooting/_index.en.md ================================================ --- title: Troubleshooting weight: 800 --- This document helps you troubleshoot Eask. {{< toc >}} ## 🚩 Possible Error Variables Some potential variables can cause faulty Eask, please check: - Emacs is installed and set up with `PATH` - Eask is installed correctly - Node version should be `14.x` or above ## ⛔️ Error when running an Eask command If you run an Eask command and get an error, there are a few things you can try: - Make sure that you have the latest Eask version. You can determine the current Eask version with `eask --version`. - Upgrade Eask with `eask upgrade-eask` or `npm install -g @emacs-eask/cli@latest` if you chose to install from `npm`. {{< hint warning >}} **⚠ Warning** If you installed Eask with **npm**, then you should probably upgrade it through **npm**. Otherwise, you would just have to ensure the **git** is installed. {{< /hint >}} * If the error persists, try to reinstall Eask from scratch. If Eask still does not work, please [report an issue](https://github.com/emacs-eask/cli/issues/new) to the issue tracker. Please include Eask output with the [--verbose 4] and [--debug] options enabled, to give us as much information as possible. ================================================ FILE: docs/content/Troubleshooting/_index.zh-tw.md ================================================ --- title: 故障排除 weight: 800 --- 本文檔幫助您排除 Eask 故障。 {{< toc >}} ## 🚩 可能的錯誤變量 一些潛在的變量可能導致錯誤的Eask,請檢查: - 用 `PATH` 安裝並設置 Emacs - Eask安裝正確 - Node 版本應為 `14.x` 或更高版本 ## ⛔️ Error when running an Eask command 如果您運行 Eask 命令並遇到錯誤,您可以嘗試一些方法: - 確保您擁有最新的 Eask 版本。 您可以使用 `eask --version` 來確定當前的 Eask 版本。 - 如果您選擇從 `npm` 安裝,請使用 `eask upgrade-eask` 或 `npm install -g @emacs-eask/cli@latest` 升級 Eask {{< hint warning >}} **⚠ 警告** 如果您使用 **npm** 安裝了 Eask,那麼您應該通過 **npm** 升級它。 否則,您只需確保安裝了 **git**。 {{< /hint >}} - 如果錯誤仍然存在,請嘗試從頭開始重新安裝 Eask。 如果Eask還是不行,請 [提交 issue](https://github.com/emacs-eask/cli/issues/new) 到 issue tracker. 請在啟用 [--verbose 4] 和 [--debug] 選項的情況下包含 Eask 輸出,為我們提供盡可能多的信息。 ================================================ FILE: docs/content/_index.en.md ================================================ --- title: geekdocNav: false geekdocAlign: center geekdocAnchor: false --- ### CLI for building, running, testing, and managing your Emacs Lisp dependencies

[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-green.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Emacs Version](https://img.shields.io/badge/Emacs-26.1+-7F5AB6.svg?logo=gnu%20emacs&logoColor=white)](https://www.gnu.org/software/emacs/download.html) [![Release](https://img.shields.io/github/release/emacs-eask/cli.svg?logo=github)](https://github.com/emacs-eask/cli/releases/latest) After installing Eask, you can easily develop any Elisp package. Out of the box, Eask supports **80+ commands** to help you throughout development. 🔥 {{< button size="large" relref="Getting-Started/Introduction/" >}}Getting Started{{< /button >}} ## 🏆 Feature overview {{< columns >}} ### Successor to Cask Eask is very similar to Cask! Eask has **all the commands** from Cask, plus even more! <---> ### Out-of-the-box Eask comes with 10+ built-in linters and test runners, so you don't have to worry about how to use it! <---> ### Eask-file is an Elisp file Eask is treated as an Elisp file. You can do anything just like you do in Emacs! {{< /columns >}} ================================================ FILE: docs/content/_index.zh-tw.md ================================================ --- title: geekdocNav: false geekdocAlign: center geekdocAnchor: false --- ### CLI 建立, 執行, 測試, 和管理你的 Emacs Lisp 依賴

[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-green.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Emacs Version](https://img.shields.io/badge/Emacs-26.1+-7F5AB6.svg?logo=gnu%20emacs&logoColor=white)](https://www.gnu.org/software/emacs/download.html) [![Release](https://img.shields.io/github/release/emacs-eask/cli.svg?logo=github)](https://github.com/emacs-eask/cli/releases/latest) 安裝 Eask 之後, 你就可以很輕鬆的開發任何 Elisp 包. 開箱及用, Eask 支援 **80+ 指令** 來幫助整個你開發. 🔥 {{< button size="large" relref="Getting-Started/Introduction/" >}}開始使用{{< /button >}} ## 🏆 功能概述 {{< columns >}} ### Cask 的繼任者 Eask 與 Cask 非常相似! Eask 擁有 Cask 的**所有命令**甚至更多! <---> ### 開箱及用 自帶 10+ 內置 linters 和 test runners,所以你不用擔心如何使用它! <---> ### Eask-file 是一個 elisp 文件 Eask 被視為 elisp 文件。 您可以像在 Emacs 中那樣做任何事情! {{< /columns >}} ================================================ FILE: docs/content/tos.en.md ================================================ --- title: Legal notice and privacy geekdocNav: false geekdocAnchor: false --- ## Contact information Jen-Chieh Shen
Email: jcs090219@gmail.com ## Privacy policy ### Collection of general data and information The website emacs-eask.github.io collects a series of general data and information when a data subject or automated system calls up the website. This general data and information are stored in the server log files. Collected may be: - the browser types and versions used - the operating system used by the accessing system - the website from which an accessing system reaches our website (so-called referrers) - the sub-websites - the date and time of access to the Internet site - an Internet protocol address (IP address) - the Internet service provider of the accessing system - any other similar data and information that may be used in the event of attacks on our information technology systems When using these general data and information, we do not draw any conclusions about the data subject. Rather, this information is needed to deliver the content of our website correctly and answer your request (Article 6.1b [GDPR][]). Your IP address and the requested URL can be stored up to 7 days due to analyze possible problems with the web page. Given that a problem appears there are cases, where it is possible that this information is stored longer than 7 days to analyze the problem in depth. They will be deleted as soon as it is confirmed that they can no further help with diagnosing the problem or the problem is solved. (Article 6.1f [GDPR][]). ### Cookies This web page does not use any cookies. [GDPR]: https://gdpr-info.eu/art-6-gdpr/ ================================================ FILE: docs/content/tos.zh-tw.md ================================================ --- title: 法律聲明和隱私 geekdocNav: false geekdocAnchor: false --- ## 聯繫信息 Jen-Chieh Shen
Email: jcs090219@gmail.com ## 隱私政策 ### 一般數據和信息的收集 當數據主體或自動化系統調用該網站時,emacs-eask.github.io 網站會收集一系列一般數據和信息。 這些一般數據和信息存儲在服務器日誌文件中。 收集的可能是: - 使用的瀏覽器類型和版本 - 訪問系統使用的操作系統 - 訪問系統訪問我們網站的網站(所謂的推薦人) - 子網站 - 訪問網站的日期和時間 - 一個互聯網協議地址(IP地址) - 接入系統的互聯網服務提供商 - 在我們的信息技術系統受到攻擊時可能會使用的任何其他類似數據和信息 使用這些一般數據和信息時,我們不會得出任何結論關於數據主體。 相反,需要此信息來提供正確瀏覽我們 網站的內容並回答您的要求 (Article 6.1b [GDPR][]). 由於分析,您的 IP 地址和請求的 URL 最多可存儲 7 天網頁可能出現的問題。 鑑於出現問題有在某些情 況下,此信息的存儲時間可能超過 7 天深入分析問題。 一經確認將立即刪除他們無法進一步幫助診斷問題 或解決問題。 (Article 6.1f [GDPR][]). ### Cookies 該網頁不使用任何 cookie。 [GDPR]: https://gdpr-info.eu/art-6-gdpr/ ================================================ FILE: docs/data/menu/extra.yaml ================================================ --- header: - name: GitHub ref: https://github.com/emacs-eask/cli icon: gdoc_github external: true - name: Discord ref: https://discord.com/invite/E9zzjWGfFD icon: gdoc_dicord external: true ================================================ FILE: docs/data/menu/more.yaml ================================================ --- more: - name: News external: true ref: "https://emacs-eask.github.io/Getting-Started/Introduction/#-news" icon: gdoc_notification - name: Releases ref: "https://github.com/emacs-eask/cli/releases" external: true icon: gdoc_download - name: "View Source" ref: "https://github.com/emacs-eask/cli" external: true icon: gdoc_github ================================================ FILE: docs/etc/execution order.drawio ================================================ 7Vjfb5swEP5reNwEOLTkMUuy7mXSpGhb2jcPX8AqYGRMIfvrZ2o7/HCrdBFS0iovke+z7+z77gtncNAya+44LpLvjEDq+C5pHLRyfH8ehvK3BfYKuHWRAmJOiYK8DtjQv6BBV6MVJVAOFgrGUkGLIRixPIdIDDDMOauHy3YsHe5a4BgsYBPh1EZ/UyIShYaB2+HfgMaJ2dlz9UyGzWINlAkmrO5BaO2gJWdMqFHWLCFtuTO8KL+vr8weDsYhF29x+OmuFpFLwsWvgpd3/sPmYSs+6ShPOK10wmtcPrZEUcmAOrfYGzI4q3ICbTzXQV/qhArYFDhqZ2tZfYklIkul5cmhjgxcQPPqkb0DEVJAwDIQfC+XaIcbTZ3WDjJU1l0lPIMlvSoYP6yLHx8id/zIgaboP+jyLbr+wI5xaPdn7LE8O2MIXRplyKIM7wTwi2HsJrw0xmYWY0uWZTgnEoQGokpQlp+dt8C7NN4C+1mW4ahUzWFH44rji2DOmw+Zm52duVuLOYskyMmibafSilJcljQa8jIkERoqtnqmHd+3+OdAW6umt2y1N0YuU9n2jZ5Xa3Zuz5bxU0cFYvXxUT1kOqziERzvhgLzGMSxNmDXt1e/4IXyGYxDKoX4NDzuSzXVO/xgVCZykM/MHf3x5iNZqDS1V/9CMA4UjALNRoEUD1agZ4kd0j5ddeHkqjtRQaeodULV+W9U3eyquglUN7+qrn/LOKo6dFXdBKozl6XpW2wnmfuemN59iw2usptCdvb7/USyO9NT62PIAo3eAFA4lSyk2X1RUsu7z3Jo/Q8= ================================================ FILE: docs/etc/scopes.drawio ================================================ ================================================ FILE: docs/layouts/partials/head/custom.html ================================================ ================================================ FILE: docs/static/custom.css ================================================ .container { padding: 0.6rem; } .gdoc-header { border-bottom: 0rem solid #8B8B8B; box-shadow: 0 0 1rem #8B8B8B; } .gdoc-brand { font-size: 2rem; font-family: serif; } .gdoc-brand__img { margin-right: 0.8rem; margin-top: 0.8rem; margin-bottom: 0.8rem; width: 3.5rem; height: 3.5rem; } .gdoc-brand__title { margin-top: 0.4rem; } .gdoc-brand__subtitle { margin-top: 0.2rem; color: #B5B5B5; font-size: 1.0rem; } /* Icons */ .gdoc_dicord { background: url('./icons/discord-mark-white.svg'); background-repeat: no-repeat; margin-top: 5px; background-size: 90%; } /* Themes */ :root[color-theme="light"] { --header-background: #4E666D; --accent-color-lite: #E3EAE6; --footer-background: #4E666D; } @media (prefers-color-scheme: light) { :root { --header-background: #4E666D; --accent-color-lite: #E3EAE6; --footer-background: #4E666D; } } :root[color-theme="dark"] { --header-background: #4E666D; --body-background: #3F3F3F; --accent-color-lite: #4F5759; --footer-background: #3F4B4E; } @media (prefers-color-scheme: dark) { :root { --header-background: #4E666D; --body-background: #3F3F3F; --accent-color-lite: #4F5759; --footer-background: #3F4B4E; } } /* * Typewriter Effect * * Copied from https://css-tricks.com/snippets/css/typewriter-effect/ */ .gdoc-brand__subtitle { overflow: hidden; white-space: nowrap; animation: typing 1.0s steps(40, end), blink-caret .75s step-end infinite; } @keyframes typing { from { width: 0 } to { width: 100% } } ================================================ FILE: docs/static/main.js ================================================ window.addEventListener('load', function () { // Code removed due to https://github.com/thegeeklab/hugo-geekdoc/pull/1003 }); ================================================ FILE: eask.js ================================================ #!/usr/bin/env node const env = require('./src/env'); const yargs = require('yargs'); const usage = `eask is the main command, used to manage your Emacs dependencies Eask is a command-line tool that helps you build, lint, and test Emacs Lisp packages. Usage: eask [options..]`; const epilogue = `For more information, find the manual at https://emacs-eask.github.io/`; const version = `0.12.10`; yargs .usage(usage) .scriptName('') .epilogue(epilogue) .commandDir('cmds/core/') .commandDir('cmds/util/') .command({ command: '*', handler() { yargs.showHelp(); } }) .version('version', 'output version information and exit', version) .help('help', 'show usage instructions') .showHidden('show-hidden', 'Show hidden commands and options') .options({ 'global': { description: `change default workspace to ~/.eask/`, alias: 'g', type: 'boolean', }, 'config': { description: `change default workspace to ~/.emacs.d/`, alias: 'c', type: 'boolean', }, 'all': { description: `enable all flag`, alias: 'a', type: 'boolean', }, 'quick': { description: `start cleanly without loading the configuration files`, alias: 'q', type: 'boolean', }, 'force': { description: `enable force flag`, alias: 'f', type: 'boolean', }, 'debug': { description: `turn on debug mode`, type: 'boolean', }, 'strict': { description: `report error instead of warnings`, type: 'boolean', }, 'allow-error': { description: `attempt to continue execution on error`, type: 'boolean', }, 'insecure': { description: `allow insecure connection`, type: 'boolean', }, 'timestamps': { description: `log with timestamps`, type: 'boolean', hidden: true, }, 'log-level': { description: `log with level`, type: 'boolean', hidden: true, }, 'log-file': { description: `generate log files`, alias: 'lf', type: 'boolean', hidden: true, }, 'elapsed-time': { description: `show elapsed time between each operation`, alias: 'et', type: 'boolean', hidden: true, }, 'no-color': { description: 'enable/disable color output', type: 'boolean', }, 'verbose': { description: `set verbosity from 0 to 5`, alias: 'v', requiresArg: true, type: 'number', }, }) .options({ 'proxy': { description: `update proxy for HTTP and HTTPS to host`, type: 'string', group: TITLE_PROXY_OPTION, }, 'http-proxy': { description: `update proxy for HTTP to host`, type: 'string', group: TITLE_PROXY_OPTION, }, 'https-proxy': { description: `update proxy for HTTPS to host`, type: 'string', group: TITLE_PROXY_OPTION, }, 'no-proxy': { description: `set no-proxy to host`, type: 'string', group: TITLE_PROXY_OPTION, }, }) .parserConfiguration({ "boolean-negation": false }) .strict() .demandCommand() .showHelpOnFail(true) .wrap(yargs.terminalWidth()) .argv; ================================================ FILE: flake.nix ================================================ { description = "Development shell for Emacs + Node.js with Eask"; inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; outputs = { self, nixpkgs }: let # Define the systems you want to support systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; in { # Generate devShells for each system devShells = nixpkgs.lib.genAttrs systems (system: let # Get the nixpkgs set for the current system pkgs = nixpkgs.legacyPackages.${system}; in { # Define the default development shell for the system default = pkgs.mkShell { buildInputs = [ pkgs.emacs pkgs.nodejs ]; shellHook = '' echo "Welcome to the Emacs development shell with Eask!" # Export `bin` to PATH. export PATH="$PATH:$PWD/bin" # Install if `node_modules` is missing if [ ! -d node_modules ]; then npm install --include=dev eask --version fi ''; }; } ); }; } ================================================ FILE: lisp/_prepare.el ================================================ ;;; _prepare.el --- Prepare for command tasks -*- lexical-binding: t; -*- ;;; Commentary: Prepare to setup Eask environment for sandboxing ;;; Code: ;; ;;; Requirement (defconst eask-required-emacs-version "26.1" "The minimum Emacs version required to run Eask.") (when (version< emacs-version eask-required-emacs-version) (error "Eask requires Emacs %s and above!" eask-required-emacs-version)) ;; ;;; Includes (require 'ansi-color) (require 'lisp-mnt) (require 'package) (require 'project) (require 'json) (require 'nsm) (require 'url-vars) (require 'cl-lib) (require 'ffap) (require 'files) (require 'ls-lisp) (require 'pp) (require 'rect) (require 'subr-x) ;; ;;; Externals (defvar ansi-inhibit-ansi) (defvar github-elpa-archive-dir) (defvar github-elpa-recipes-dir) (defvar package-build-default-files-spec) (declare-function package-build-expand-files-spec "ext:package-build.el") (declare-function github-elpa-build "ext:github-elpa.el") (declare-function ansi-red "ext:ansi.el") (declare-function ansi-blue "ext:ansi.el") (declare-function ansi-green "ext:ansi.el") (declare-function ansi-yellow "ext:ansi.el") (declare-function ansi-white "ext:ansi.el") ;; ;;; Environment ;; Determine the underlying operating system (defconst eask-is-windows (memq system-type '(cygwin windows-nt ms-dos)) "The system is Windows.") (defconst eask-is-mac (eq system-type 'darwin) "The system is macOS.") (defconst eask-is-linux (eq system-type 'gnu/linux) "The system is GNU Linux.") (defconst eask-is-bsd (or eask-is-mac (eq system-type 'berkeley-unix)) "The system is BSD.") (defconst eask-system-type (cond (eask-is-windows 'dos) (eask-is-bsd 'mac) (eask-is-linux 'unix) (t 'unknown)) "Return current OS type.") (setq make-backup-files nil) (setq package-enable-at-startup nil ; To avoid initializing twice package-check-signature nil package-archives nil ; Leave it to custom use package-archive-priorities nil) (defvar eask-dot-emacs-file nil "Variable hold .emacs file location.") (defun eask--load--adv (fnc &rest args) "Prevent `_prepare.el' loading twice. Arguments FNC and ARGS are used for advice `:around'." (unless (string= (nth 0 args) (eask-script "_prepare")) (apply fnc args))) (advice-add 'load :around #'eask--load--adv) (defconst eask-has-colors (getenv "EASK_HASCOLORS") "Return non-nil if terminal supports colors.") (defconst eask-homedir (getenv "EASK_HOMEDIR") "Eask's home directory path. It points to the global home directory `~/.eask/'.") (defconst eask-userdir (expand-file-name "../" eask-homedir) "Eask's user directory path. It points to the global user directory `~/'.") (defconst eask-package-sys-dir (expand-file-name (concat emacs-version "/elpa/") eask-homedir) "Eask global elpa directory; it will be treated as the system-wide packages. It points to the global elpa directory `~/.eask/XX.X/elpa/'.") (defconst eask-invocation (getenv "EASK_INVOCATION") "Eask's invocation program path.") (defconst eask-is-pkg (getenv "EASK_IS_PKG") "Return non-nil if Eask is packaged.") (defconst eask-rest (let ((args (getenv "EASK_REST_ARGS"))) (setq args (ignore-errors (split-string args ","))) args) "Eask's arguments after command separator `--'; return a list. If the argument is `-- arg0 arg1'; it will return `(arg0 arg1)'.") (defun eask-rest () "Eask's arguments after command separator `--'; return a string. If the argument is `-- arg0 arg1'; it will return `arg0 arg1'." (mapconcat #'identity eask-rest " ")) (defcustom eask-import-timeout 10 "Number of seconds before timing out elisp importation attempts. If nil, never time out." :type '(choice (number :tag "Number of seconds") (const :tag "Never time out" nil)) :group 'eask) ;; This is used to avoid the error: ;; ;; Ignoring unknown mode `eask-mode'. ;; (unless (fboundp 'eask-mode) (define-derived-mode eask-mode emacs-lisp-mode "Eask")) ;; ;;; Execution (defconst eask-argv argv "This stores the real argv; the argv will soon be replaced with `(eask-args)'.") (defconst eask--script (nth 1 (or (member "-scriptload" command-line-args) (member "-l" command-line-args))) "Script currently executing.") (defconst eask-lisp-root (let* ((script (ignore-errors (file-name-directory eask--script))) (dir (ignore-errors (expand-file-name (concat script "../")))) (basename (file-name-nondirectory (directory-file-name dir))) (root (expand-file-name "/"))) (while (and (not (string= root dir)) (not (string= basename "lisp"))) (setq dir (expand-file-name (concat dir "../")) basename (file-name-nondirectory (directory-file-name dir)))) dir) "Source `lisp' directory; should always end with slash.") (defun eask-command () "What's the current command? If the command is with subcommand, it will return command with concatenate with slash separator. For example, the following: $ eask lint checkdoc [FILES..] will return `lint/checkdoc' with a dash between two subcommands." (let* ((script-dir (file-name-directory eask--script)) (script-file (file-name-sans-extension (file-name-nondirectory eask--script))) (module-name (eask-s-replace eask-lisp-root "" script-dir)) (module-names (split-string module-name "/" t))) ;; Make certain commands the root command; e.g. `core', `checker', etc. (if (member (nth 0 module-names) '("core" "checker")) script-file (mapconcat #'identity (append module-names (list script-file)) "/")))) (defun eask-command-check (version) "Report error if the current command requires minimum VERSION." (when (version< emacs-version version) (eask-error "The command `%s' requires Emacs %s and above!" (eask-s-replace "/" " " (eask-command)) ; Pretty print. version))) (defun eask-command-p (commands) "Return t when the current command matches any entry in the COMMANDS list." (member (eask-command) (eask-listify commands))) (defun eask-special-p () "Return t if the command that can be run without Eask-file existence. These commands will first respect the current workspace. If the current workspace has no valid Eask-file; it will load the Eask-file form the global workspace instead. If there is no valid Eask-file presented; the execution continues without printing the missing Eask-file message." (eask-command-p '("init" "init/source" "init/cask" "init/eldev" "init/keg" "create/package" "create/elpa" "create/el-project" "bump" "cat" "keywords" "repl" "generate/ignore" "generate/license" "test/melpazoid" "util/root"))) (defun eask-execution-p () "Return t if the command is the execution command. This is added because we don't want to pollute `error' and `warn' functions." (eask-command-p '("load" "exec" "emacs" "eval" "repl" "run/script" "run/command" ;; NOTE: These test commands handle the exit code themselves; ;; therefore, we don't need to handle it for them! "test/ert" "test/ert-runner" "test/buttercup"))) (defun eask-checker-p () "Return t if running Eask as the checker. Without this flag, the process will be terminated once the error is occurred. This flag allows you to run through operations without reporting errors." (eask-command-p '("analyze"))) (defun eask-script (script) "Return full SCRIPT filename." (concat eask-lisp-root script ".el")) (defvar eask-loading-file-p nil "This became t; if we are loading script from another file and not expecting the `eask-start' execution.") (defun eask-load (script) "Load another eask SCRIPT; so we can reuse functions across all scripts." (let ((eask-loading-file-p t)) (eask-call script))) (defun eask-call (script) "Call another eask SCRIPT." (if-let* ((script-file (eask-script script)) ((file-exists-p script-file))) (load script-file nil t) (eask-error "Script missing %s" script-file))) (defun eask-import (url) "Load and eval the script from a URL." (with-current-buffer (url-retrieve-synchronously url t nil eask-import-timeout) (goto-char (point-min)) (re-search-forward "^$" nil 'move) (forward-char) (delete-region (point-min) (point)) (eval-buffer))) ;; ;;; Compat (defun eask-always (&rest _arguments) "The function `always' is supported after Emacs 28.1." t) ;; ;;; Util (defmacro eask-add-hook (hooks &rest body) "The eye candy for the function `add-hook'." (declare (indent 1)) `(cond ((listp ,hooks) (dolist (hook ,hooks) (add-hook hook (lambda (&optional arg0 arg1 arg2 &rest args) ,@body)))) (t (add-hook ,hooks (lambda (&optional arg0 arg1 arg2 &rest args) ,@body))))) (defmacro eask-defvc< (version &rest body) "Define scope if Emacs version is below VERSION. Argument BODY are forms for execution." (declare (indent 1) (debug t)) `(when (< emacs-major-version ,version) ,@body)) (defmacro eask--silent (&rest body) "Execute BODY without message." (declare (indent 0) (debug t)) `(let ((inhibit-message t) message-log-max) ,@body)) (defmacro eask--unsilent (&rest body) "Execute BODY with message." (declare (indent 0) (debug t)) `(let (inhibit-message) ,@body)) (defun eask-2str (obj) "Convert OBJ to string." (format "%s" obj)) (defun eask-2url (url) "Convert secure/insecure URL." (if (and url (gnutls-available-p) (eask-network-insecure-p)) (eask-s-replace "https://" "http://" url) url)) (defun eask-listify (obj) "Turn OBJ to list." (if (listp obj) obj (list obj))) (defun eask-intern (obj) "Safely intern OBJ." (if (stringp obj) (intern obj) obj)) (defun eask--sinr (len-or-list form-1 form-2) "If LEN-OR-LIST has length of 1; return FORM-1, else FORM-2." (let ((len (if (numberp len-or-list) len-or-list (length len-or-list)))) (if (<= len 1) form-1 form-2))) ;; This is used to creating the directory recipe! (defun eask-current-time () "Return current time." (let ((now (current-time))) (logior (ash (car now) 16) (cadr now)))) (defun eask-seq-str-max (sequence) "Return max length in SEQUENCE of strings." (let ((result 0)) (mapc (lambda (elm) (setq result (max result (length (eask-2str elm))))) sequence) result)) (defun eask-s-replace (old new s) "Replace OLD with NEW in S each time it occurs." (replace-regexp-in-string (regexp-quote old) new s t t)) (defun eask-f-filename (path) "Return the name of PATH." (file-name-nondirectory (directory-file-name path))) (defun eask-directory-empty-p (dir) "Return t if DIR names an existing directory containing no other files. The function `directory-empty-p' only exists 28.1 or above; copied it." (if (fboundp #'directory-empty-p) (directory-empty-p dir) (and (file-directory-p dir) ;; XXX: Do not pass in the 5th argument COUNT; it doesn't compatbile to ;; 27.2 or lower! (null (directory-files dir nil directory-files-no-dot-files-regexp t))))) (defun eask--guess-package-name (basename) "Convert the BASENAME to a valid, commonly seen package name." (when-let* ((name (ignore-errors (downcase basename)))) (setq name (eask-s-replace "emacs-" "" name) name (eask-s-replace "-emacs" "" name) name (replace-regexp-in-string "[.-]el$" "" name)) name)) (defun eask-guess-package-name (&optional basename) "Return the possible package name. Optional argument BASENAME accepts a string; it will convert the string to a valid, commonly seen package name." (or (eask--guess-package-name basename) (eask-package-name) (eask--guess-package-name (ignore-errors (file-name-nondirectory (file-name-sans-extension eask-package-file)))))) (defun eask-guess-entry-point (&optional basename) "Return the guess entry point by its BASENAME." (let ((name (eask-guess-package-name basename))) (format "%s.el" name))) (defun eask-read-string (prompt &optional initial-input history default-value inherit-input-method) "Wrapper for function `read-string'. Argument PROMPT and all optional arguments INITIAL-INPUT, HISTORY, DEFAULT-VALUE and INHERIT-INPUT-METHOD see function `read-string' for more information." (let ((str (read-string prompt initial-input history default-value inherit-input-method))) (eask-s-replace "\"" "" str))) (defun eask--goto-line (line) "Go to LINE." (goto-char (point-min)) (forward-line (1- line))) (defun eask--column-at-point (point) "Get column at POINT." (save-excursion (goto-char point) (current-column))) (defconst eask-buffer-name "*eask*" "Buffer name is used for temporary storage throughout the life cycle.") (defmacro eask-with-buffer (&rest body) "Create a temporary buffer (for this program), and evaluate BODY there." (declare (indent 0) (debug t)) `(with-current-buffer (get-buffer-create ,eask-buffer-name) ,@body)) (defmacro eask-with-temp-buffer (&rest body) "Create a temporary buffer (for this program), and evaluate BODY there." (declare (indent 0) (debug t)) `(eask-with-buffer (erase-buffer) ,@body)) (defun eask-re-seq (regexp string) "Get a list of all REGEXP matches in a STRING." (save-match-data (let ((pos 0) matches) (while (string-match regexp string pos) (push (match-string 0 string) matches) (setq pos (match-end 0))) (reverse matches)))) ;; ;;; Color (defmacro eask--with-no-color (&rest body) "Execute forms BODY in when no color output." (declare (indent 0) (debug t)) `(let ((ansi-inhibit-ansi t)) ,@body)) (defun eask-ansi-codes (s) "Return a list of ansi codes from S." (eask-re-seq ansi-color-control-seq-regexp s)) (defun eask-s-replace-ansi (old new s) "Like the function `eask-s-replace' but work with ansi. For arguments OLD, NEW and S; see the function `eask-s-replace' for more information." (if-let* ((splits (split-string s (regexp-quote old)))) (let* ((reset "\e[0m") (result (nth 0 splits)) (index 1) (last)) (while (< index (length splits)) (let* ((data (eask-ansi-codes result))) (setq last (car (last data)) result (concat result (if (null last) "" reset) new last (nth index splits)))) (cl-incf index)) ;; Just ensure the last character is reset. (concat result (if (null last) "" reset))) (eask-s-replace old new s))) ;; ;;; Progress (defcustom eask-elapsed-time nil "Log with elapsed time." :type 'boolean :group 'eask) (defcustom eask-minimum-reported-time 0.1 "Minimal load time that will be reported." :type 'number :group 'eask) (defmacro eask-with-progress (msg-start body msg-end) "Progress BODY wrapper with prefix (MSG-START) and suffix (MSG-END) messages." (declare (indent 0) (debug t)) `(if eask-elapsed-time (let ((now (current-time))) (ignore-errors (eask-write ,msg-start)) ,body (let ((elapsed (float-time (time-subtract (current-time) now)))) (if (< elapsed eask-minimum-reported-time) (ignore-errors (eask-msg ,msg-end)) (ignore-errors (eask-write ,msg-end)) (eask-msg " (%.3fs)" elapsed)))) (ignore-errors (eask-write ,msg-start)) ,body (ignore-errors (eask-msg ,msg-end)))) (defun eask-progress-seq (prefix sequence suffix func) "Shorthand to progress SEQUENCE of task. Arguments PREFIX and SUFFIX are strings to print before and after each progress. Argument FUNC are execution for eash progress; this is generally the actual task work." (let* ((total (length sequence)) (count 0) (offset (eask-2str (length (eask-2str total))))) (mapc (lambda (item) (cl-incf count) (eask-with-progress (format (concat "%s [%" offset "d/%d] %s... ") prefix count total (ansi-green item)) (when func (funcall func item)) suffix)) sequence))) (defun eask-print-log-buffer (&optional buffer-or-name) "Loop through each line and print each line with corresponds log level. You can pass BUFFER-OR-NAME to replace current buffer." (with-current-buffer (or buffer-or-name (current-buffer)) (goto-char (point-min)) (while (not (eobp)) (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) ;; The variable `line' can contains format specifier, avoid it with `%s'! (cond ((string-match-p "[: ][Ee]rror: " line) (eask-ignore-errors (eask-error "%s" line))) ((string-match-p "[: ][Ww]arning: " line) (eask-warn "%s" line)) (t (eask-log "%s" line)))) (forward-line 1)))) (defun eask-delete-file (filename) "Delete a FILENAME from disk." (let (deleted) (eask-with-progress (format "Deleting %s... " filename) (eask-with-verbosity 'log (setq deleted (file-exists-p filename)) (ignore-errors (delete-file filename)) (setq deleted (and deleted (not (file-exists-p filename))))) (if deleted "done ✓" "skipped ✗")) deleted)) ;; ;;; Action (defvar eask--action-prefix "" "The prefix to display before each package action.") (defvar eask--action-index 0 "The index ID for each task.") (defun eask--action-format (len) "Construct action format by LEN." (setq len (eask-2str len)) (concat "[%" (eask-2str (length len)) "d/" len "] ")) ;; ;;; Archive (defconst eask-package-archives-url-format "https://raw.githubusercontent.com/emacs-eask/archives/master/%s/" "The backup package archives url.") (defun eask--locate-archive-contents (archive) "Locate ARCHIVE's contents file." (let* ((name (cond ((consp archive) (car archive)) (t archive))) (file "archive-contents") (dir (expand-file-name (concat "archives/" name) package-user-dir))) (expand-file-name file dir))) (defun eask--package-download-one-archive (fnc &rest args) "Execution around function `package-download-one-archive'. Arguments FNC and ARGS are used for advice `:around'." (cl-incf eask--action-index) (let* ((archive (nth 0 args)) (name (car archive)) (url (cdr archive)) (fmt (eask--action-format (length package-archives))) (download-p)) (eask-with-verbosity-override 'log (when (= 1 eask--action-index) (eask-msg "")) (eask-with-progress (format " - %sDownloading %s (%s)... " (format fmt eask--action-index) (ansi-green (eask-2str name)) (ansi-yellow (eask-2str url))) (eask-with-verbosity 'debug (apply fnc args) (setq download-p t)) (cond (download-p "done ✓") (t "failed ✗")))))) (defun eask--download-archives () "If archives download failed; download it manually." (let ((archives (cl-remove-if (lambda (archive) ;; First, exclude the `local' archive. (equal (car archive) eask--local-archive-name)) package-archives)) (one-download-p)) (dolist (archive archives) (cl-incf eask--action-index) (let* ((name (car archive)) (local-file (eask--locate-archive-contents archive)) (dir (file-name-directory local-file)) ; ~/.emacs.d/elpa/archives/{name}/ (contents (file-name-nondirectory local-file)) ; archive-contents (url (format eask-package-archives-url-format name)) (url-file (concat url contents)) (download-p) (fmt (eask--action-format (length archives)))) (unless (file-exists-p local-file) (eask-with-verbosity-override 'log (when (= 1 eask--action-index) (eask-println "")) (eask-with-progress (format " - %sDownloading %s (%s) manually... " (format fmt eask--action-index) (ansi-green name) (ansi-yellow url)) (eask-with-verbosity 'debug (if (url-file-exists-p url-file) (progn (ignore-errors (make-directory dir t)) (url-copy-file url-file local-file t) (setq download-p t one-download-p t)) (eask-debug "No archive-contents found in `%s'" (ansi-green name)))) (cond (download-p "done ✓") (t "failed ✗"))))))) (when one-download-p (eask-pkg-init t)))) ;; ;;; Package (defun eask-use-legacy-package-build () "Return t if using the legacy `package-build' package." (version< emacs-version "27.1")) (defun eask-load-legacy-package-build () "Load the legacy `package-build' package." (when (eask-use-legacy-package-build) (add-to-list 'load-path (format "%sextern/package-build/%s/" eask-lisp-root emacs-major-version) t))) (eask-load-legacy-package-build) (defun eask--update-exec-path () "Add all bin directory to the variable `exec-path'." (when-let* (((file-exists-p package-user-dir)) (entries (directory-files package-user-dir t directory-files-no-dot-files-regexp))) (dolist (entry entries) (when-let* ((bin (expand-file-name "bin" entry)) ((file-directory-p bin))) (add-to-list 'exec-path bin t))) (delete-dups exec-path))) (defun eask--update-load-path () "Add all .el files to the variable `load-path'." (dolist (filename (eask-package-el-files)) (add-to-list 'load-path (file-name-directory filename) t)) (delete-dups load-path)) (defun eask-dependencies () "Return list of dependencies." (append eask-depends-on (and (eask-dev-p) eask-depends-on-dev))) (defun eask--package-mapc (func deps) "Like function `mapc' but for process package transaction specifically. For arguments FUNC and DEPS, see function `mapc' for more information." (let* ((eask--action-prefix) ; remain untouch (len (length deps)) (fmt (eask--action-format len)) (count 0)) (dolist (dep deps) (cl-incf count) (setq eask--action-prefix (format fmt count)) (funcall func dep)))) (defun eask--install-dep (dep) "Custom install DEP." (let ((name (car dep))) (cond ;; Install through the function `package-install-file'. ((memq :file dep) (let ((file (nth 2 dep))) (eask-package-install-file name file))) ;; Install through the function `package-vc-install'. ((memq :vc dep) (let ((spec (cdr (memq :vc dep)))) (eask-package-vc-install name spec))) ;; Try out packages using `try'. ((memq :try dep) (let ((url-or-package (nth 2 dep))) (eask-package-try name url-or-package))) ;; Fallback to archive install. (t (eask-package-install name))))) (defun eask--install-deps (dependencies msg) "Install DEPENDENCIES. Argument MSG are extra information to display in the header; mainly define the scope of the dependencies (it's either `production' or `development')." (let* ((names (mapcar #'car dependencies)) (names (mapcar #'eask-intern names)) (len (length dependencies)) (ies (eask--sinr len "y" "ies")) (pkg-installed (cl-remove-if #'package-installed-p names)) (installed (length pkg-installed)) (skipped (- len installed))) (eask-log "Installing %s %s dependenc%s..." len msg ies) (eask-msg "") (eask--package-mapc #'eask--install-dep dependencies) (eask-msg "") (eask-info "(Total of %s dependenc%s installed, %s skipped)" installed ies skipped))) (defun eask-install-dependencies () "Install dependencies defined in Eask file." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (when eask-depends-on-recipe-p (eask-log "Installing required external packages...") (eask-archive-install-packages '("gnu" "melpa") 'package-build) (eask-with-progress "Building temporary archives (this may take a while)... " (eask-with-verbosity 'debug (github-elpa-build)) "done ✓") (eask-pkg-init t)) (when eask-depends-on (eask--install-deps eask-depends-on "package")) (when (and eask-depends-on-dev (eask-dev-p)) (eask-msg "") (eask--install-deps eask-depends-on-dev "development"))) (defun eask-setup-paths () "Setup both variables `exec-path' and `load-path'." (eask-with-progress (ansi-green "Updating environment variables... ") (eask-with-verbosity 'debug (eask--update-exec-path) (eask--update-load-path) (setenv "PATH" (string-join exec-path path-separator)) (setenv "EMACSLOADPATH" (string-join load-path path-separator))) (ansi-green "done ✓"))) (defvar eask--package-initialized nil "Flag for package initialization in global scope.") (defun eask-pkg-init (&optional force) "Package initialization. If the argument FORCE is non-nil, force initialize packages in this session." (when (or (not package--initialized) (not package-archive-contents) force ;; XXX: we need to initialize once in global scope since most Emacs ;; configuration would likely to set `package-archives' variable ;; themselves. (and (eask-config-p) (not eask--package-initialized))) (setq eask--package-initialized t) (eask-with-progress (ansi-green "Loading package information... ") (eask-with-verbosity 'debug (package-initialize t) (let ((eask--action-index 0)) (package-refresh-contents)) (let ((eask--action-index 0)) (eask--download-archives))) (ansi-green "done ✓")))) (defun eask--pkg-transaction-vars (pkg) "Return 1 symbol and 2 strings. Argument PKG is the name of the package." (let* (;; Ensure symbol (pkg (eask-intern pkg)) ;; Wrap package name with color (pkg-string (ansi-green (eask-2str pkg))) ;; Wrap version number with color (pkg-version (ansi-yellow (eask-package--version-string pkg)))) (list pkg pkg-string pkg-version))) (defmacro eask--pkg-process (pkg &rest body) "Execute BODY with PKG's related variables." (declare (indent 1) (debug t)) `(let* ((pkg-info (eask--pkg-transaction-vars ,pkg)) (pkg (nth 0 pkg-info)) (name (nth 1 pkg-info)) (version (nth 2 pkg-info)) (installed-p (package-installed-p pkg)) (should-reinstall-p (and installed-p (eask-force-p)))) ,@body)) (defmacro eask-with-archives (archives &rest body) "Scope that temporary make ARCHIVES available. Argument BODY are forms for execution." (declare (indent 1) (debug t)) `(let ((package-archives package-archives) (archives (eask-listify ,archives)) (package-user-dir eask-package-sys-dir) ; Install as global packages. (added)) (dolist (archive archives) (unless (assoc archive package-archives) (setq added t) (eask-with-progress (format "Adding required archives (%s)... " (ansi-yellow archive)) (eask-f-source archive) "done ✓"))) (when added (eask-with-progress "Refresh archives information... " (eask--silent (eask-pkg-init t)) "done ✓")) ,@body)) (defun eask-archive-install-packages (archives &rest names) "Install package NAMES with ARCHIVES setup." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (when (cl-some (lambda (pkg) (not (package-installed-p pkg))) names) (eask-with-archives archives (eask--package-mapc #'eask-package-install names)))) (defun eask-package-installable-p (pkg) "Return non-nil if package (PKG) is installable." (assq (eask-intern pkg) package-archive-contents)) (defun eask-package-try (pkg &optional url-or-package) "To try a package (PKG) without actually install it. The optional argument URL-OR-PACKAGE is used in the function `try'." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask-with-progress (format " - %sTrying out %s%s... " eask--action-prefix (ansi-green (eask-2str pkg)) (if url-or-package (concat " in " (ansi-yellow url-or-package)) "")) (eask-with-verbosity 'debug (eask-archive-install-packages '("gnu" "melpa") 'try) (require 'try) (try (or url-or-package pkg))) "done ✓")) (defun eask--package-delete-before-install (pkg force) "Make sure PKG is not presented before installing the latest. The argument FORCE is passed through to the `package-delete' function." ;; Recipe can be `nil', handle it. (when-let* ((rcp (eask-package-desc pkg t))) (package-delete rcp force) t)) (defun eask-package-vc-install (pkg spec) "To vc install the package (PKG) by argument SPEC." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask--pkg-process pkg (cond ((and installed-p (not should-reinstall-p)) (eask-with-progress (format " - %sSkipping %s (%s)... " eask--action-prefix name version) (progn ) ; no operation needed "already installed ✗")) (t (eask-with-progress (format " - %s%snstalling %s (%s)... " eask--action-prefix (if should-reinstall-p "Rei" "I") name version) (eask-with-verbosity 'debug ;; Handle `--force` flag. (when should-reinstall-p (eask--package-delete-before-install pkg t)) ;; Install it. (apply #'package-vc-install spec)) "done ✓"))))) (defun eask-package-install-file (pkg file) "To FILE install the package (PKG)." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask--pkg-process pkg (cond ((and installed-p (not should-reinstall-p)) (eask-with-progress (format " - %sSkipping %s (%s)... " eask--action-prefix name version) (progn ) ; no operation needed "already installed ✗")) (t (eask-with-progress (format " - %s%snstalling %s (%s)... " eask--action-prefix (if should-reinstall-p "Rei" "I") name version) (eask-with-verbosity 'debug ;; Handle `--force` flag. (when should-reinstall-p (eask--package-delete-before-install pkg t)) ;; Install it. (package-install-file (expand-file-name file))) "done ✓"))))) (defun eask-package-install (pkg) "Install the package (PKG)." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask--pkg-process pkg (cond ((and installed-p (not should-reinstall-p)) (eask-with-progress (format " - %sSkipping %s (%s)... " eask--action-prefix name version) (progn ) ; no operation needed "already installed ✗")) ((progn (eask-pkg-init) (unless (eask-package-installable-p pkg) (eask-error "Package not installable `%s'; make sure the package archive (source) is included" pkg)))) (t (eask--pkg-process pkg ; Second call to force refresh the data. (eask-with-progress (format " - %s%snstalling %s (%s)... " eask--action-prefix (if should-reinstall-p "Rei" "I") name version) (eask-with-verbosity 'debug ;; Handle `--force` flag. (when should-reinstall-p (eask--package-delete-before-install pkg t)) ;; Install it. (let ((current-prefix-arg (eask-force-p))) (package-install pkg))) "done ✓")))))) (defun eask-package-delete (pkg) "Delete the package (PKG)." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask--pkg-process pkg (cond ((not installed-p) (eask-with-progress (format " - %sSkipping %s (%s)... " eask--action-prefix name version) (progn ) ; no operation needed "not installed ✗")) (t (eask--pkg-process pkg ; Second call to force refresh the data. (let ((success)) (eask-with-progress (format " - %sUninstalling %s (%s)... " eask--action-prefix name version) (eask-with-verbosity 'debug (when (eask--package-delete-before-install pkg (eask-force-p)) (setq success t))) (if success "done ✓" "skipped ✗")))))))) (defun eask-package-reinstall (pkg) "Reinstall the package (PKG)." (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask--pkg-process pkg (cond ;; You cannot reinstall the package that are not installed. ((not installed-p) (eask-with-progress (format " - %sSkipping %s (%s)... " eask--action-prefix name version) (progn ) ; no operation needed "not installed ✗")) (t (eask-pkg-init) (eask--pkg-process pkg ; Second call to force refresh the data. (eask-with-progress (format " - %sReinstalling %s (%s)... " eask--action-prefix name version) (eask-with-verbosity 'debug (eask--package-delete-before-install pkg t) (let ((current-prefix-arg (eask-force-p))) (package-install pkg))) "done ✓")))))) (defun eask-package-desc (name &optional current) "Build package description by its NAME. The argument NAME must be the package's name. If the argument CURRENT is non-nil, we retrieve version number from the variable `package-alist'; otherwise, we retrieve it from the variable `package-archive-contents'." (cadr (assq name (if current package-alist (or package-archive-contents package-alist))))) (defun eask-package--version (name &optional current) "Return package's version. For arguments NAME and CURRENT, please see function `eask-package-desc' for full detials." (when-let* ((desc (eask-package-desc name current))) (package-desc-version desc))) (defun eask-package--version-string (pkg) "Return PKG's version." (if-let* ((version (or (eask-package--version pkg t) (eask-package--version pkg nil)))) (package-version-join version) ;; Just in case, but this should never happens! "0")) (defun eask-package-desc-url () "Return url from package descriptor." (when eask-package-desc (when-let* ((extras (package-desc-extras eask-package-desc))) (cdr (assoc :url extras))))) (defun eask-package-desc-keywords () "Return keywords from package descriptor." (when eask-package-desc (or (package-desc--keywords eask-package-desc) ;; XXX: Handle Emacs 26.x keywords cannot be parsed issue. (and eask-package-file (with-temp-buffer (insert-file-contents eask-package-file) (lm-keywords-list)))))) (defun eask-pkg-el () "Return package description file if exists." (let ((pkg-el (package--description-file default-directory))) (when (file-readable-p pkg-el) pkg-el))) ;; ;;; Flags (defun eask--str2num (str) "Convert string (STR) to number." (ignore-errors (string-to-number str))) (defun eask--flag (flag) "Return non-nil if FLAG exists." (member (concat "--eask" flag) eask-argv)) (defun eask--flag-value (flag) "Return value for FLAG." (nth 1 (eask--flag flag))) ;;; Boolean (defun eask-global-p () "Non-nil when in global space (`-g', `--global')." (eask--flag "-g")) (defun eask-config-p () "Non-nil when in config space (`-c', `--config')." (eask--flag "-c")) (defun eask-local-p () "Non-nil when in local space (default)." (and (not (eask-global-p)) (not (eask-config-p)))) (defun eask-all-p () "Non-nil when flag is on (`-a', `--all')." (eask--flag "-a")) (defun eask-quick-p () "Non-nil when flag is on (`-q', `--quick')." (eask--flag "-q")) (defun eask-force-p () "Non-nil when flag is on (`-f', `--force')." (eask--flag "-f")) (defun eask-dev-p () "Non-nil when flag is on (`--dev', `--development')." (eask--flag "--dev")) (defun eask-debug-p () "Non-nil when flag is on (`--debug')." (eask--flag "--debug")) (defun eask-strict-p () "Non-nil when flag is on (`--strict')." (eask--flag "--strict")) (defun eask-timestamps-p () "Non-nil when flag is on (`--timestamps')." (eask--flag "--timestamps")) (defun eask-log-level-p () "Non-nil when flag is on (`--log-level')." (eask--flag "--log-level")) (defun eask-log-file-p () "Non-nil when flag is on (`--log-file', `--lf')." (eask--flag "--log-file")) (defun eask-elapsed-time-p () "Non-nil when flag is on (`--elapsed-time', `--et')." (eask--flag "--elapsed-time")) (defun eask-allow-error-p () "Non-nil when flag is on (`--allow-error')." (eask--flag "--allow-error")) (defun eask-insecure-p () "Non-nil when flag is on (`--insecure')." (eask--flag "--insecure")) (defun eask-no-color-p () "Non-nil when flag is on (`--no-color')." (eask--flag "--no-color")) (defun eask-clean-p () "Non-nil when flag is on (`-c', `--clean')." (eask--flag "--clean")) (defun eask-json-p () "Non-nil when flag is on (`--json')." (eask--flag "--json")) (defun eask-number-p () "Non-nil when flag is on (`-n', `--number')." (eask--flag "--number")) (defun eask-yes-p () "Non-nil when flag is on (`--yes')." (eask--flag "--yes")) ;;; String (with arguments) (defun eask-output () "Non-nil when flag has value (`--o', `--output')." (eask--flag-value "--output")) (defun eask-proxy () "Non-nil when flag has value (`--proxy')." (eask--flag-value "--proxy")) ; --proxy (defun eask-http-proxy () "Non-nil when flag has value (`--http-proxy')." (eask--flag-value "--http-proxy")) (defun eask-https-proxy () "Non-nil when flag has value (`--https-proxy')." (eask--flag-value "--https-proxy")) (defun eask-no-proxy () "Non-nil when flag has value (`--no-proxy')." (eask--flag-value "--no-proxy")) (defun eask-destination () "Non-nil when flag has value (`--dest', `--destination')." (eask--flag-value "--dest")) (defalias 'eask-dest #'eask-destination "Shorthand for function `eask-destination'.") (defun eask-from () "Non-nil when flag has value (`--from')." (eask--flag-value "--from")) ;;; Number (with arguments) (defun eask-depth () "Non-nil when flag has value (`--depth')." (eask--str2num (eask--flag-value "--depth"))) (defun eask-verbose () "Non-nil when flag has value (`-v', `--verbose')." (eask--str2num (eask--flag-value "--verbose"))) (defun eask--handle-global-options () "Handle global options." (when (eask-debug-p) (setq debug-on-error t)) (when (eask-verbose) (setq eask-verbosity (eask-verbose))) (when (eask-insecure-p) (setq network-security-level 'low)) (when (eask-timestamps-p) (setq eask-timestamps t)) (when (eask-log-level-p) (setq eask-log-level t)) (when (eask-log-file-p) (setq eask-log-file t)) (when (eask-elapsed-time-p) (setq eask-elapsed-time t)) (when (eask-no-color-p) (setq ansi-inhibit-ansi t)) (unless eask-has-colors (setq ansi-inhibit-ansi t)) (when (display-graphic-p) (setq ansi-inhibit-ansi t)) (eask--add-proxy "http" (eask-proxy)) (eask--add-proxy "https" (eask-proxy)) (eask--add-proxy "http" (eask-http-proxy)) (eask--add-proxy "https" (eask-https-proxy)) (eask--add-proxy "no_proxy" (eask-no-proxy))) ;; ;;; Proxy (defun eask--add-proxy (protocal host) "Add proxy. Argument PROTOCAL and HOST are used to construct scheme." (when host (push (cons protocal (eask-proxy)) url-proxy-services))) ;; ;;; Core (defvar eask--first-init-p nil "Is non-nil if .eask does not exists; meaning users haven't called eask in the current workspace.") (defvar eask--initialized-p nil "Set to t once the environment setup has done; this is used when calling other scripts internally. See function `eask-call'.") (defun eask--form-options (options) "Add --eask to all OPTIONS." (mapcar (lambda (elm) (concat "--eask" elm)) options)) (defconst eask--option-switches (eask--form-options '("-g" "-c" "-a" "-q" "-f" "--dev" "--debug" "--strict" "--allow-error" "--insecure" "--timestamps" "--log-level" "--log-file" "--elapsed-time" "--no-color" "--clean" "--json" "--number" "--yes")) "List of boolean type options.") (defconst eask--option-args (eask--form-options '("--output" "--proxy" "--http-proxy" "--https-proxy" "--no-proxy" "--verbose" "--silent" "--depth" "--dest" "--from")) "List of arguments (number/string) type options.") (defconst eask--command-list (append eask--option-switches eask--option-args) "List of commands to accept, so we can avoid unknown option error.") (defun eask-self-command-p (arg) "Return non-nil if ARG is known internal command." (member arg eask--command-list)) (defun eask-argv (index) "Return argument value by INDEX." (elt eask-argv index)) (defun eask-argv-out () "Convert all internal arguments to external arguments. Simply remove `--eask' for each option, like `--eask--strict' to `--strict'." (mapcar (lambda (arg) (eask-s-replace "--eask" "" arg)) eask-argv)) (defun eask-args (&optional index) "Get all arguments except options If the optional argument INDEX is non-nil, return the element." (let ((argv (cl-remove-if (lambda (arg) (member arg eask--option-switches)) eask-argv)) (args) (skip-next)) (dolist (arg argv) (if skip-next (setq skip-next nil) (if (member arg eask--option-args) (setq skip-next t) (push arg args)))) (setq args (reverse args)) (if index (nth 0 args) args))) (defmacro eask--batch-mode (&rest body) "Execute forms BODY in batch-mode." (declare (indent 0) (debug t)) `(let ((argv (eask-args)) load-file-name buffer-file-name) ,@body)) (defmacro eask--setup-env (&rest body) "Execute BODY with workspace setup." (declare (indent 0) (debug t)) `(eask--batch-mode (let ((alist)) (dolist (cmd eask--command-list) (push (cons cmd (lambda (&rest _))) alist)) (setq command-switch-alist (append command-switch-alist alist)) ,@body))) (defconst eask-file-keywords '("package" "website-url" "keywords" "author" "license" "package-file" "package-descriptor" "files" "script" "source" "source-priority" "depends-on" "development" "exec-paths" "load-paths") "List of Eask file's DSL keywords.") (defun eask--loop-file-keywords (func) "Loop through Eask file keywords for environment replacement. Argument FUNC is a function we execute while this function will provide the new and old function name. Internal used for function `eask--alias-env'." (dolist (keyword eask-file-keywords) (let ((keyword-sym (intern keyword)) (api (intern (concat "eask-f-" keyword))) ; existing function (old (intern (concat "eask--f-" keyword)))) ; variable that holds function pointer (funcall func keyword-sym api old)))) (defmacro eask--alias-env (&rest body) "Replace all Eask file functions temporary; this is only used when loading Eask file in the workspace. Argument BODY are forms for execution." (declare (indent 0) (debug t)) `(let (result) ;; XXX: Magic here is we replace all keyword functions with `eask-xxx'... (eask--loop-file-keywords (lambda (keyword api old) (defalias old (symbol-function keyword)) (defalias keyword (symbol-function api)))) (setq result (progn ,@body)) ;; XXX: after loading Eask file, we revert those functions back to normal! (eask--loop-file-keywords (lambda (keyword _api old) (defalias keyword (symbol-function old)))) result)) (defun eask-working-directory () "Return the working directory of the program going to be executed." (cond ((eask-config-p) user-emacs-directory) ((eask-global-p) (expand-file-name "../../" user-emacs-directory)) (t default-directory))) (defvar eask-file nil "The Eask file's filename.") (defvar eask-file-root nil "The Eask file's directory.") (defun eask-root-del (filename) "Remove Eask file root path from FILENAME." (when (stringp filename) (eask-s-replace (or eask-file-root default-directory) "" filename))) (defun eask-file-load (location &optional noerror) "Load Eask file in the LOCATION. Argument NOERROR is passed through function `load'; therefore, please see the function `load' for more detials." (when-let* ((target-eask-file (expand-file-name location user-emacs-directory)) (result (eask--alias-env (load target-eask-file noerror t t)))) (setq eask-file target-eask-file ; assign eask file only if success eask-file-root (file-name-directory target-eask-file)) (run-hooks 'eask-file-loaded-hook) result)) (defun eask--match-file (name) "Check to see if NAME is our target Eask-file, then return it." (let (;; Ensure path to filename (name (file-name-nondirectory (directory-file-name name))) ;; `p-' stands for pattern (p-easkfile-full (format "Easkfile.%s" emacs-version)) (p-easkfile-major (format "Easkfile.%s" emacs-major-version)) (p-easkfile "Easkfile") (p-eask-full (format "Eask.%s" emacs-version)) (p-eask-major (format "Eask.%s" emacs-major-version)) (p-eask "Eask")) (car (member name (list p-easkfile-full p-easkfile-major p-easkfile p-eask-full p-eask-major p-eask))))) (defun eask--all-files (&optional dir) "Return a list of Eask files from DIR. If argument DIR is nil, we use `default-directory' instead." (setq dir (or dir default-directory)) (when-let* ((files (append (ignore-errors (directory-files dir t "Easkfile[.0-9]*\\'")) (ignore-errors (directory-files dir t "Eask[.0-9]*\\'")))) (files (cl-remove-if #'file-directory-p files))) (cl-remove-if-not #'eask--match-file files))) (defun eask--find-files (start-path) "Find the Eask-file from START-PATH. This uses function `locate-dominating-file' to look up directory tree." (when-let* (;; XXX: This is redundant, but the simplest way to find the root path! (root (locate-dominating-file start-path #'eask--all-files)) (files (eask--all-files root)) ; get all available Eask-files ;; Filter it to restrict to this Emacs version! (files (cl-remove-if-not #'eask--match-file files)) ;; Make `Easkfile.29.1' > `Easkfile.29' > `Easkfile' (same with `Eask' file) (files (sort files #'string-greaterp)) ;; Make `Easkfile' > `Eask' higher precedent! (files (sort files (lambda (item1 item2) (and (string-prefix-p "Easkfile" item1) (not (string-prefix-p "Easkfile" item2))))))) files)) (defun eask-file-try-load (start-path) "Try load the Eask-file in START-PATH." (when-let* ((files (eask--find-files start-path)) (file (car files))) ;; Revert printing behaviour when loading Eask-file. (eask--unsilent (eask-file-load file)))) (defmacro eask--with-hooks (&rest body) "Execute BODY with before/after hooks." (declare (indent 0) (debug t)) `(let* ((command (eask-command)) (before (concat "eask-before-" command "-hook")) (after (concat "eask-after-" command "-hook"))) (eask--unsilent (run-hooks 'eask-before-command-hook) (run-hooks (intern before)) ,@body (run-hooks (intern after)) (run-hooks 'eask-after-command-hook)))) (defmacro eask--setup-home (dir &rest body) "Set up config directory in DIR, then execute BODY." (declare (indent 1) (debug t)) `(let* ((user-emacs-directory (expand-file-name (concat ".eask/" emacs-version "/") ,dir)) (package-user-dir (expand-file-name "elpa/" user-emacs-directory)) ;; Add global scope elpa directory. (package-directory-list (append package-directory-list (list eask-package-sys-dir))) (early-init-file (locate-user-emacs-file "early-init.el")) (eask-dot-emacs-file (locate-user-emacs-file ".emacs")) (user-init-file (locate-user-emacs-file "init.el")) (custom-file (locate-user-emacs-file "custom.el"))) ,@body)) ;; NOTE: If you modified this function, make sure you modified `core/emacs.el' ;; file as well! (defun eask--load-config () "Load configuration if valid." (let ((inhibit-config (eask-quick-p))) (eask-with-progress (ansi-green "Loading configuration... ") ;; Revert printing behaviour when loading user files. (eask--unsilent (unless inhibit-config ;; `early-init.el' is supported after 27.1 (when (version<= "27" emacs-version) (load early-init-file t t)) (load eask-dot-emacs-file t t) (load user-init-file t t))) (ansi-green (if inhibit-config "skipped ✗" "done ✓"))))) (defmacro eask-start (&rest body) "Execute BODY with workspace setup." (declare (indent 0) (debug t)) `(unless eask-loading-file-p (if eask--initialized-p (progn ,@body) (setq eask--initialized-p t) (eask--setup-env (eask--handle-global-options) (cond ((eask-config-p) (let ((early-init-file (locate-user-emacs-file "early-init.el")) (eask-dot-emacs-file (locate-user-emacs-file "../.emacs")) (user-init-file (locate-user-emacs-file "init.el"))) ;; We accept Eask-file in `config' scope, but it shouldn't be used ;; for the sandbox. (eask-with-verbosity 'debug (if (eask-file-try-load user-emacs-directory) (eask-msg "✓ Loading config Eask file in %s... done!" eask-file) (eask-msg "✗ Loading config Eask file... missing!")) (eask-msg "")) (package-activate-all) (ignore-errors (make-directory package-user-dir t)) (eask--silent (eask-setup-paths)) (eask--load-config) (eask--with-hooks ,@body))) ((eask-global-p) (eask--setup-home eask-userdir (let ((eask--first-init-p (not (file-directory-p user-emacs-directory)))) ;; We accept Eask-file in `global' scope, but it shouldn't be used ;; for the sandbox. (eask-with-verbosity 'debug (eask-ignore-errors ; Eask-file is optional! (if (eask-file-try-load eask-homedir) (eask-msg "✓ Loading global Eask file in %s... done!" eask-file) (eask-msg "✗ Loading global Eask file... missing!"))) (eask-msg "")) (package-activate-all) (ignore-errors (make-directory package-user-dir t)) (eask--silent (eask-setup-paths)) (eask-with-verbosity 'debug (eask--load-config)) (eask--with-hooks ,@body)))) ((eask-special-p) ; Commands without Eask-file needed! ;; First, try to find a valid Eask-file! (eask-file-try-load default-directory) ;; Then setup the user directory according to the Eask-file! (eask--setup-home (or eask-file-root eask-userdir) (let ((eask--first-init-p (not (file-directory-p user-emacs-directory))) (scope (if eask-file-root "" "global "))) (eask-with-verbosity 'debug (eask-ignore-errors ; Again, without Eask-file needed! (if (or eask-file-root (eask-file-try-load eask-homedir)) (eask-msg "✓ Loading %sEask file in %s... done!" scope eask-file) (eask-msg "✗ Loading %sEask file... missing!" scope))) (eask-msg "")) (package-activate-all) (ignore-errors (make-directory package-user-dir t)) (eask--silent (eask-setup-paths)) (eask-with-verbosity 'debug (eask--load-config)) (eask--with-hooks ,@body)))) (t (eask--setup-home nil ; `nil' is the `default-directory' (let ((eask--first-init-p (not (file-directory-p user-emacs-directory)))) (eask-with-verbosity 'debug (if (eask-file-try-load default-directory) (eask-msg "✓ Loading Eask file in %s... done!" eask-file) (eask-msg "✗ Loading Eask file... missing!")) (eask-msg "")) (if (not eask-file) (eask-help "core/init") (package-activate-all) (ignore-errors (make-directory package-user-dir t)) (eask--silent (eask-setup-paths)) (eask-with-verbosity 'debug (eask--load-config)) (eask--with-hooks ,@body)))))) ;; Report exit stats if any. (eask--resolve-exit-status))))) (defun eask--resolve-exit-status () "Resolve current exit status." (when (memq 'error (eask--error-status)) (eask--exit 'failure))) ;; ;;; Eask file (defun eask-network-insecure-p () "Are we attempt to use insecure connection?" (eq network-security-level 'low)) (defconst eask-source-mapping `((gnu . "https://elpa.gnu.org/packages/") (nongnu . "https://elpa.nongnu.org/nongnu/") (celpa . "https://celpa.conao3.com/packages/") (eine . "https://emacs-eine.github.io/elpa/packages/") (jcs-elpa . "https://jcs-emacs.github.io/jcs-elpa/packages/") (marmalade . "https://marmalade-repo.org/packages/") (melpa . "https://melpa.org/packages/") (melpa-stable . "https://stable.melpa.org/packages/") (org . "https://orgmode.org/elpa/") (shmelpa . "https://shmelpa.commandlinesystems.com/packages/") (ublt . "https://elpa.ubolonton.org/packages/") ;; Devel (gnu-devel . "https://elpa.gnu.org/devel/") (nongnu-devel . "https://elpa.nongnu.org/nongnu-devel/")) "Mapping of source name and url.") (defun eask-source-url (name &optional location) "Get the source url by it's NAME and LOCATION." (setq location (or location (cdr (assq (intern (eask-2str name)) eask-source-mapping))) location (eask-2url location)) location) (defvar eask-package nil) (defvar eask-package-desc nil) ; package descriptor (defvar eask-package-descriptor nil) (defvar eask-website-url nil) (defvar eask-keywords nil) (defvar eask-authors nil) (defvar eask-licenses nil) (defvar eask-package-file nil) (defvar eask-files nil) (defvar eask-scripts nil) (defvar eask-depends-on-emacs nil) (defvar eask-depends-on nil) (defvar eask-depends-on-dev nil) (defmacro eask--save-eask-file-state (&rest body) "Execute BODY without touching the Eask-file global variables." (declare (indent 0) (debug t)) `(let (package-archives package-archive-priorities eask-file eask-file-root eask-package eask-package-desc eask-website-url eask-keywords eask-authors eask-licenses eask-package-file eask-package-descriptor eask-files eask-scripts eask-depends-on-emacs eask-depends-on eask-depends-on-dev) ,@body)) (defmacro eask--save-load-eask-file (file success &rest error) "Load an Eask FILE and execute forms SUCCESS or ERROR." (declare (indent 2) (debug t)) `(eask--save-eask-file-state (eask--setup-env (eask--alias-env (if (let ((default-directory (file-name-directory ,file))) (ignore-errors (eask-file-load ,file))) (progn ,success) ,@error))))) (defun eask-package--get (key) "Return package info by KEY." (plist-get eask-package key)) (defun eask-package-name () "Return current package's name." (eask-package--get :name)) (defun eask-package-version () "Return current package's version number." (eask-package--get :version)) (defun eask-package-description () "Return current package's description." (eask-package--get :description)) (defun eask-depends-emacs-version () "Get Eask-file Emacs version string." (nth 0 (cdar eask-depends-on-emacs))) (defun eask-f-package (name version description) "Set the package information. Argument NAME is the name of the package. VERSION is the string contains valid version number. DESCRIPTION is the package description." (if eask-package (eask-error "✗ Multiple definition of `package'") (setq eask-package `(:name ,name :version ,version :description ,description)) (progn ; Run checker (eask--checker-string "Name" name) (version= version "0.1.0") (eask--checker-string "Description" description)))) (defun eask-f-website-url (url) "Set website URL." (if eask-website-url (eask-error "✗ Multiple definition of `website-url'") (setq eask-website-url url))) (defun eask-f-keywords (&rest keywords) "Set package KEYWORDS." (if eask-keywords (eask-error "✗ Multiple definition of `keywords'") (setq eask-keywords keywords))) (defun eask-f-author (name &optional email) "Set package author's NAME and EMAIL." (if (member name (mapcar #'car eask-authors)) (eask-warn "💡 Warning regarding duplicate author name, %s" name) (when (and email (not (string-match-p "@" email))) (eask-warn "💡 Email seems to be invalid, %s" email)) (push (cons name email) eask-authors))) (defun eask-f-license (name) "Set package license NAME." (if (member name eask-licenses) (eask-warn "💡 Warning regarding duplicate license name, %s" name) (push name eask-licenses))) (defun eask--try-construct-package-desc (file) "Try construct the package descriptor from FILE." (let (skipped) (with-temp-buffer (insert-file-contents file) (setq eask-package-desc (ignore-errors (cond ((string-suffix-p "-pkg.el" file) ; if ensure -pkg.el (package--read-pkg-desc 'dir)) ((eask-pkg-el) ; if -pkg.el is presented, (setq skipped t) nil) ; skip it (t (package-buffer-info)))))) ; default read main package file (eask-with-verbosity 'debug (eask-msg (concat (if eask-package-desc "✓ " "✗ ") "Try constructing the package-descriptor (%s)... " (cond (eask-package-desc "succeeded!") (skipped "skipped!") (t "failed!"))) (file-name-nondirectory file))))) (defun eask-f-package-file (file) "Set package FILE." (if eask-package-file (eask-error "✗ Multiple definition of `package-file'") (setq eask-package-file (expand-file-name file)) (if (file-exists-p eask-package-file) (eask--try-construct-package-desc eask-package-file) (eask-warn "💡 Package-file seems to be missing `%s'" file)) (when-let* (((and (not eask-package-descriptor) ; prevent multiple definition error (not eask-package-desc))) ; check if constructed (pkg-file (eask-pkg-el))) (eask-f-package-descriptor pkg-file) ;; XXX: Make sure DSL package descriptor is set back to `nil' (setq eask-package-descriptor nil)))) (defun eask-f-package-descriptor (pkg-file) "Set package PKG-FILE." (cond (eask-package-descriptor (eask-error "✗ Multiple definition of `package-descriptor'")) ((and eask-package-desc ; check if construct successfully (equal (eask-pkg-el) pkg-file)) ; check filename the same ) ; ignore (t (setq eask-package-descriptor (expand-file-name pkg-file)) (cond ((not (string-suffix-p "-pkg.el" eask-package-descriptor)) (eask-error "✗ Pkg-file must end with `-pkg.el'")) ((not (file-exists-p eask-package-descriptor)) (eask-warn "💡 Pkg-file seems to be missing `%s'" pkg-file)) (t (eask--try-construct-package-desc eask-package-descriptor)))))) (defun eask-f-files (&rest patterns) "Set files PATTERNS." (setq eask-files (append eask-files patterns))) (defun eask-f-script (name command &rest args) "Add a script command. Argument NAME is the command id, and cannot be repeated. Argument COMMAND is a string contain shell commands. The rest arguments ARGS is a list of string contains extra shell commands, and it will eventually be concatenate with the argument COMMAND." (when (symbolp name) (setq name (eask-2str name))) ; ensure to string, accept symbol (when (assoc name eask-scripts) (eask-error "✗ Run-script with the same key name is not allowed: `%s`" name)) (push (cons name (mapconcat #'identity (append (list command) args) " ")) eask-scripts)) (defun eask-f-source (name &optional location) "Add archive NAME with LOCATION." (when (symbolp name) (setq name (eask-2str name))) ; ensure to string, accept symbol ;; Handle local archive. (when (equal name eask--local-archive-name) (eask-error "✗ Invalid archive name `%s'" name)) ;; Handle multiple same archive name! (when (assoc name package-archives) (eask-error "✗ Multiple definition of source `%s'" name)) (setq location (eask-source-url name location)) (unless location (eask-error "✗ Unknown package archive `%s'" name)) (add-to-list 'package-archives (cons name location) t)) (defun eask-f-source-priority (name &optional priority) "Add PRIORITY for to NAME." (when (symbolp name) (setq name (eask-2str name))) ; ensure to string, accept symbol (add-to-list 'package-archive-priorities (cons name priority) t)) (defvar eask-depends-on-recipe-p nil "Set to t if package depends on recipe.") (defvar eask--local-archive-name "local" "The local archive name.") (defun eask--setup-dependencies () "Setup dependencies list." (setq eask-depends-on (reverse eask-depends-on) eask-depends-on-dev (reverse eask-depends-on-dev)) ;; On recipe (when eask-depends-on-recipe-p (eask-with-progress (concat (ansi-green "✓ Checking local archives `") (ansi-magenta eask--local-archive-name) (ansi-green "`... ")) (eask-with-verbosity 'debug ;; Make sure can be customized by `source' (unless (assoc eask--local-archive-name package-archives) (add-to-list 'package-archives `(,eask--local-archive-name . ,github-elpa-archive-dir) t)) ;; Make sure can be customized by `source-priority' (unless (assoc eask--local-archive-name package-archive-priorities) ;; If the local archives is added, we set the priority to a very ;; high number so user we always use the specified dependencies! (add-to-list 'package-archive-priorities `(,eask--local-archive-name . 90) t))) (ansi-green "done!")))) (add-hook 'eask-file-loaded-hook #'eask--setup-dependencies) (defun eask--check-depends-on (recipe) "Return non-nil if RECIPE is invalid." (let ((pkg (car recipe)) (minimum-version (cdr recipe))) (cond ((member recipe eask-depends-on) (eask-error "✗ Define dependencies with the same name `%s'" pkg)) ((cl-some (lambda (rcp) (string= (car rcp) pkg)) eask-depends-on) (eask-error "✗ Define dependencies with the same name `%s' with different version" pkg))))) (defun eask-f-depends-on (pkg &rest args) "Specify a dependency (PKG) of this package. Argument PKG is the name of that dependency. ARGS can either be a string contains the version number or a list contains recipe information (for local ELPA)." (cond ((string= pkg "emacs") (if eask-depends-on-emacs (eask-error "✗ Define dependencies with the same name `%s'" pkg) (let* ((minimum-version (car args)) (recipe (list pkg minimum-version))) (if (version< emacs-version minimum-version) (eask-error "✗ This requires Emacs %s and above!" minimum-version) (push recipe eask-depends-on-emacs)) recipe))) ;; Specified packages ((or (memq :file args) ; File packages (memq :vc args) ; VC packages (memq :try args)) ; Try packages (let* ((recipe (append (list (intern pkg)) args))) (unless (eask--check-depends-on recipe) (push recipe eask-depends-on)) recipe)) ;; No argument specify ((<= (length args) 1) (let* ((minimum-version (car args)) (recipe (list pkg minimum-version))) (unless (eask--check-depends-on recipe) (push recipe eask-depends-on)) recipe)) ;; recipe are entered (t (let ((recipe (append (list (intern pkg)) args))) (unless (eask--check-depends-on recipe) (push recipe eask-depends-on) (eask-load "extern/github-elpa") (eask-with-verbosity 'debug (eask-with-progress (ansi-blue (format "Generating recipe for package %s... " (ansi-yellow pkg))) (write-region (pp-to-string recipe) nil (expand-file-name pkg github-elpa-recipes-dir)) (ansi-blue "done ✓"))) (setq eask-depends-on-recipe-p t)) recipe)))) (defun eask-f-development (&rest dep) "Development scope with list of DEP." (setq dep (cl-remove-if #'null dep)) ; make sure no `nil', see #143 (dolist (pkg dep) (push pkg eask-depends-on-dev) (delete-dups eask-depends-on-dev) (setq eask-depends-on (remove pkg eask-depends-on)))) (defun eask-f-exec-paths (&rest dirs) "Add all DIRS to the variable `exec-path'." (dolist (dir dirs) (add-to-list 'exec-path (expand-file-name dir) t))) (defun eask-f-load-paths (&rest dirs) "Add all DIRS to to the variable `load-path'." (dolist (dir dirs) (add-to-list 'load-path (expand-file-name dir) t))) ;; ;;; Verbosity (defcustom eask-verbosity 3 "Log level for all messages; 4 means trace most anything, 0 means nothing. Standard is, 0 (error), 1 (warning), 2 (info), 3 (log), 4 (debug), 5 (all)." :type 'integer :group 'eask) (defcustom eask-timestamps nil "Log messages with timestamps." :type 'boolean :group 'eask) (defcustom eask-log-level nil "Log messages with level." :type 'boolean :group 'eask) (defcustom eask-level-color '((all . ansi-magenta) (debug . ansi-blue) (log . ansi-white) (info . ansi-cyan) (warn . ansi-yellow) (error . ansi-red)) "Alist of each log level's color, in (SYMBOL . ANSI-FUNCTION)." :type 'alist :group 'eask) (defun eask--verb2lvl (symbol) "Convert verbosity SYMBOL to level." (cl-case symbol (`all 5) (`debug 4) (`log 3) (`info 2) (`warn 1) (`error 0) (t symbol))) (defun eask-reach-verbosity-p (symbol) "Return t if SYMBOL reach verbosity (should be printed)." (>= eask-verbosity (eask--verb2lvl symbol))) (defmacro eask-with-verbosity (symbol &rest body) "Define verbosity scope. Execute forms BODY limit by the verbosity level (SYMBOL)." (declare (indent 1) (debug t)) `(if (eask-reach-verbosity-p ,symbol) (progn ,@body) (eask--silent ,@body))) (defmacro eask-with-verbosity-override (symbol &rest body) "Define override verbosity scope. Execute forms BODY limit by the verbosity level (SYMBOL)." (declare (indent 1) (debug t)) `(if (eask-reach-verbosity-p ,symbol) (eask--unsilent ,@body) (eask--silent ,@body))) (defun eask--ansi (symbol string) "Paint STRING with color defined by log level (SYMBOL)." (if-let* ((ansi-function (cdr (assq symbol eask-level-color)))) ;; The `%s` is use to avoid `not enough arguments for string` error. (funcall ansi-function "%s" string) string)) (defun eask--format (prefix fmt &rest args) "Format Eask messages. Argument PREFIX is a string identify the type of this messages. Arguments FMT and ARGS are used to pass through function `format'." (concat (when eask-timestamps (format-time-string "%Y-%m-%d %H:%M:%S ")) (when eask-log-level (concat prefix " ")) (apply #'format fmt args))) (defun eask--msg (symbol prefix fmt &rest args) "If level (SYMBOL) is at or below `eask-verbosity'; then, log the message. For arguments PREFIX, FMT and ARGS, please see funtion `eask--format' for the detials." (eask-with-verbosity symbol (let* ((output (apply #'eask--format prefix fmt args)) (output (eask--ansi symbol output)) (output (eask--msg-displayable-kwds output)) ; Don't color, but replace it! (func (cl-case symbol ((or error warn) symbol) (t #'message)))) (funcall func "%s" output)))) (defun eask-debug (fmt &rest args) "Send debug message; see function `eask--msg' for arguments FMT and ARGS." (apply #'eask--msg 'debug "[DEBUG]" fmt args)) (defun eask-log (fmt &rest args) "Send log message; see function `eask--msg' for arguments FMT and ARGS." (apply #'eask--msg 'log "[LOG]" fmt args)) (defun eask-info (fmt &rest args) "Send info message; see function `eask--msg' for arguments FMT and ARGS." (apply #'eask--msg 'info "[INFO]" fmt args)) (defun eask-warn (fmt &rest args) "Send warn message; see function `eask--msg' for arguments FMT and ARGS." (apply #'eask--msg 'warn "[WARNING]" fmt args)) (defun eask-error (fmt &rest args) "Send error message; see function `eask--msg' for arguments FMT and ARGS." (apply #'eask--msg 'error "[ERROR]" fmt args)) (defun eask--msg-char-displayable (char replacement s) "Ensure CHAR is displayable in S; if not, we fallback to REPLACEMENT character." (if (char-displayable-p (string-to-char char)) s (eask-s-replace char replacement s))) (defun eask--msg-displayable-kwds (s) "Make sure all keywords is displayable in S." (let* ((s (eask--msg-char-displayable "✓" "v" s)) (s (eask--msg-char-displayable "✗" "X" s)) (s (eask--msg-char-displayable "💡" "" s))) s)) (defun eask--msg-paint-kwds (s) "Paint keywords from S." (let* ((s (eask-s-replace-ansi "✓" (ansi-green "✓") s)) (s (eask-s-replace-ansi "✗" (ansi-red "✗") s)) (s (eask-s-replace-ansi "💡" (ansi-yellow "💡") s))) s)) (defun eask--format-paint-kwds (msg &rest args) "Paint keywords after format MSG and ARGS." (let* ((s (apply #'format msg args)) (s (eask--msg-paint-kwds s)) (s (eask--msg-displayable-kwds s))) s)) (defun eask-princ (object &optional stderr) "Like function `princ'; with flag STDERR. For argument OBJECT, please see function `princ' for the detials. If optional argument STDERR is non-nil; use stderr instead." (unless inhibit-message (princ object (when stderr #'external-debugging-output)))) (defun eask-print (msg &rest args) "Standard output printing without newline. For arguments MSG and ARGS, please see function `eask--format-paint-kwds' for the detials." (eask-princ (apply #'eask--format-paint-kwds msg args))) (defun eask-println (msg &rest args) "Like the function `eask-print' but contains the newline at the end. For arguments MSG and ARGS, please see function `eask-print' for the detials." (apply #'eask-print (concat msg "\n") args)) (defun eask-msg (msg &rest args) "Like the function `message' but replace unicode with color. For arguments MSG and ARGS, please see function `eask--format-paint-kwds' for the detials." (apply #'eask-write msg args) (eask-princ "\n" t)) (defun eask-write (msg &rest args) "Like the function `eask-msg' but without newline at the end. For arguments MSG and ARGS, please see function `eask-msg' for the detials." (eask-princ (apply #'eask--format-paint-kwds msg args) t)) (defun eask-report (&rest args) "Report error/warning depends on strict flag. Argument ARGS are direct arguments for functions `eask-error' or `eask-warn'." (apply (if (eask-strict-p) #'eask-error #'eask-warn) args)) ;; ;;; Exit Code (defconst eask--exit-code `((success . 0) ; Unused (failure . 1) ; Catchall for general errors (misuse . 2)) "The exit code specification.") (defun eask-exit-code (key) "Return the exit code by KEY symbol." (alist-get key eask--exit-code)) (defun eask--exit (&optional exit-code &rest _) "Kill Emacs with EXIT-CODE (default 1)." (kill-emacs (or (cond ((numberp exit-code) exit-code) ((symbolp exit-code) (eask-exit-code exit-code))) (eask-exit-code 'failure)))) ;; ;;; Error Handling (defvar eask--ignore-error-p nil "Don't trigger error when this is non-nil.") (defvar eask-inhibit-error-message nil "Non-nil to stop error/warning message.") (defvar eask--has-error-p nil "Non-nil if an error has occurred.") (defvar eask--has-warn-p nil "Non-nil if a warning has occurred.") (defmacro eask-ignore-errors (&rest body) "Execute BODY without killing the process." (declare (indent 0) (debug t)) `(let ((eask--ignore-error-p t)) ,@body)) (defmacro eask--silent-error (&rest body) "Execute BODY and inhibit all error messages." (declare (indent 0) (debug t)) `(let ((eask-inhibit-error-message t)) ,@body)) (defmacro eask-ignore-errors-silent (&rest body) "Execute BODY by completely ignore errors." (declare (indent 0) (debug t)) `(eask-ignore-errors (eask--silent-error ,@body))) (defun eask--error-status () "Return error status." (let ((result)) ;; Error. (when eask--has-error-p (push 'error result)) ;; Warning. (when eask--has-warn-p (push (if (eask-strict-p) 'error 'warn) result)) ;; No repeat. (delete-dups result))) (defun eask--trigger-error (args) "Trigger error event. The argument ARGS is passed from the function `eask--error'." (cond ((< emacs-major-version 28) ;; But we can remove this after Emacs 28, since function `find-library-name' ;; has replaced the function `signal' instead of the `error'. ;; ;; Handle https://github.com/emacs-eask/cli/issues/11. (unless (string-prefix-p "Can't find library " (car args)) (setq eask--has-error-p t))) ;; Flag the error normally. (t (setq eask--has-error-p t))) ; Just a record. (when (and eask--has-error-p (not eask--ignore-error-p) (not (eask-allow-error-p)) ;; Ignore when checking Eask-file. (not (eask-checker-p))) ;; Stop immediately. (eask--exit 'failure))) (defun eask--error (fnc &rest args) "On error. Arguments FNC and ARGS are used for advice `:around'." (let ((msg (eask--ansi 'error (apply #'format-message args)))) (unless eask-inhibit-error-message (eask--unsilent (eask-msg "%s" msg))) (run-hook-with-args 'eask-on-error-hook 'error msg) (eask--trigger-error args)) (when debug-on-error (apply fnc args))) (defun eask--trigger-warn () "Trigger warning event." (setq eask--has-warn-p t)) ; Just a record. (defun eask--warn (fnc &rest args) "On warn. Arguments FNC and ARGS are used for advice `:around'." (let ((msg (eask--ansi 'warn (apply #'format-message args)))) (unless eask-inhibit-error-message (eask--unsilent (eask-msg "%s" msg))) (run-hook-with-args 'eask-on-warning-hook 'warn msg) (eask--trigger-warn)) (eask--silent (apply fnc args))) ;; Don't pollute outer exection. (unless (eask-execution-p) (advice-add 'error :around #'eask--error) (advice-add 'warn :around #'eask--warn)) ;; ;;; Log (defconst eask-log-path ".log" "Directory path to create log files.") (defcustom eask-log-file nil "Weather to generate log files." :type 'boolean :group 'eask) (defmacro eask--log-write-buffer (buffer file) "Write BUFFER to FILE." `(when (get-buffer-create ,buffer) (let ((buffer-file-coding-system 'utf-8)) (write-region (with-current-buffer ,buffer (buffer-string)) nil (expand-file-name ,file log-dir))))) (eask-add-hook '( kill-emacs-hook) (when eask-log-file ; Write log files (let ((log-dir (expand-file-name eask-log-path eask-file-root))) (make-directory log-dir t) (eask--log-write-buffer "*Messages*" "messages.log") (eask--log-write-buffer "*Warnings*" "warnings.log") (eask--log-write-buffer "*Backtrace*" "backtrace.log") (eask--log-write-buffer "*Compile-Log*" "compile-log.log")))) ;; ;;; File (defun eask-files-spec () "Return files spec." (or eask-files package-build-default-files-spec)) (defun eask-expand-file-specs (specs) "Expand file SPECS." (mapcar (lambda (elm) (expand-file-name (car elm) default-directory)) (ignore-errors ; The new files spec will trigger error, wrap it (package-build-expand-files-spec nil nil default-directory specs)))) (defun eask-package-files () "Return package files in workspace." (let ((files (eask-expand-file-specs (eask-files-spec)))) ;; Package file is part of package-files (when eask-package-file (push eask-package-file files)) (delete-dups files) (setq files (cl-remove-if-not #'file-exists-p files)) (unless files (eask-debug "No matching file(s) found in %s: %s" default-directory (eask-files-spec))) files)) (defun eask-package-el-files () "Return package files in workspace." (cl-remove-if-not (lambda (filename) (string= (file-name-extension filename) "el")) (eask-package-files))) (defun eask-package-elc-files () "Return package files' elc in workspace." (when-let* ((elcs (mapcar (lambda (elm) (concat elm "c")) (eask-package-el-files)))) (setq elcs (cl-remove-if-not (lambda (elm) (file-exists-p elm)) elcs)) elcs)) (defun eask-package-multi-p () "Return t if multi-files package." (or (bound-and-true-p package-build-build-function) (< 1 (length (eask-package-files))))) (defun eask-package-single-p () "Return t if single file package." (not (eask-package-multi-p))) (defun eask-unpacked-size () "Return unpacked size." (let ((size 0)) (dolist (filename (eask-package-files)) (cl-incf size (file-attribute-size (file-attributes filename)))) (string-trim (ls-lisp-format-file-size size t)))) ;; ;;; Help (defun eask--help-display () "Display help instruction." (goto-char (point-min)) (let ((max-column 0)) (while (not (eobp)) (forward-line 1) (beginning-of-line) (insert " ") (end-of-line) (setq max-column (max (current-column) max-column))) (eask-msg (concat "''" (spaces-string max-column) "''")) (eask-msg (buffer-string)) (eask-msg (concat "''" (spaces-string max-column) "''")))) (defun eask-help (command &optional print-or-exit-code) "Show COMMAND's help instruction. When the optional variable PRINT-OR-EXIT-CODE is a number, it will exit with that code. Set to non-nil would just print the help message without sending the exit code. The default value `nil' will be replaced by `1'; therefore would send exit code of `1'." (let* ((command (eask-2str command)) ; convert to string (help-file (concat eask-lisp-root "help/" command)) ;; The default exit code is `2' since `eask-help' prints the help ;; message on user error 99% of the time. (print-or-exit-code (or print-or-exit-code (eask-exit-code 'misuse)))) (if (file-exists-p help-file) (with-temp-buffer (insert-file-contents help-file) (unless (string-empty-p (buffer-string)) (let ((buf-str (eask--msg-displayable-kwds (buffer-string)))) (erase-buffer) (insert buf-str)) (eask--help-display)) ;; Exit with code if needed (cond ((numberp print-or-exit-code) (eask--exit print-or-exit-code)) (t ))) ; Don't exit with anything else. (eask-error "✗ Help manual missing `%s`" help-file)))) ;; ;;; Checker (defun eask--checker-existence () "Return errors if required metadata is missing." (unless eask-package (eask-error (concat "✗ Missing metadata package; make sure you have created " "an Eask-file with `$ eask init`!")))) (defun eask--check-strings (fmt f p &rest args) "Test strings (F and P); then print FMT and ARGS if not equal." (unless (string= f p) (apply #'eask-warn (append (list fmt f p) args)))) (defun eask--check-optional (f p msg1 msg2 msg3 msg4) "Conditional way to check optional headers, URL and KEYWORDS. For arguments F and P, please see function `eask--check-strings' for more information. Arguments MSG1, MSG2, MSG3 and MSG4 are conditional messages." (cond ((and f p) (eask--check-strings msg1 f p)) (f (eask-warn msg2)) (p (eask-warn msg3)) (t (eask-warn msg4)))) (defun eask--checker-metadata () "Report warnings if metadata doesn't match." (when-let* (((and eask-package eask-package-desc)) (def-point (if (eask-pkg-el) "-pkg.el file" "package-file"))) (eask--check-strings "💡 Unmatched package name `%s`; it should be `%s`" (eask-package-name) (package-desc-name eask-package-desc)) (when-let* ((ver-eask (eask-package-version)) (ver-pkg (package-desc-version eask-package-desc)) ;; `package-version-join' returns only one of the possible ;; inverses, since `version-to-list' is a many-to-one operation ((not (equal (version-to-list ver-eask) ver-pkg)))) (eask--check-strings "💡 Unmatched version `%s`; it should be `%s`" ver-eask (package-version-join ver-pkg))) (eask--check-strings "💡 Unmatched summary `%s`; it should be `%s`" (eask-package-description) (package-desc-summary eask-package-desc)) (let ((url (eask-package-desc-url))) (eask--check-optional eask-website-url url "💡 Unmatched website URL `%s`; it should be `%s`" (format "💡 Unmatched website URL `%s`; add `%s` to %s" eask-website-url (if (string-prefix-p "-pkg.el" def-point) (format ":url \"%s\"" eask-website-url) (format ";; URL: %s" eask-website-url)) def-point) (format "💡 Unmatched website URL `%s`; add `(website-url \"%s\")` to Eask-file" url url) (format "💡 URL header is optional, but it's often recommended"))) (let ((keywords (eask-package-desc-keywords))) (cond ((or keywords eask-keywords) (dolist (keyword keywords) (unless (member keyword eask-keywords) (eask-warn "💡 Unmatched keyword `%s`; add `(keywords \"%s\")` to Eask-file or consider removing it" keyword keyword))) (dolist (keyword eask-keywords) (unless (member keyword keywords) (eask-warn "💡 Unmatched keyword `%s`; add `%s` to %s or consider removing it" keyword (if (string-prefix-p "-pkg.el" def-point) (format ":keywords '(\"%s\")" keyword) (format ";; Keywords: %s" keyword)) def-point)))) (t (eask-warn "💡 Keywords header is optional, but it's often recommended")))) (let* ((dependencies (append eask-depends-on-emacs eask-depends-on)) (dependencies (mapcar #'car dependencies)) (dependencies (mapcar (lambda (elm) (eask-2str elm)) dependencies)) (requirements (package-desc-reqs eask-package-desc)) (requirements (mapcar #'car requirements)) (requirements (mapcar (lambda (elm) (eask-2str elm)) requirements))) (dolist (req requirements) (unless (member req dependencies) (eask-warn "💡 Unmatched dependency `%s`; add `(depends-on \"%s\")` to Eask-file or consider removing it" req req))) (dolist (dep dependencies) (unless (member dep requirements) (eask-warn "💡 Unmatched dependency `%s`; add `(%s \"VERSION\")` to %s or consider removing it" dep dep def-point)))))) (add-hook 'eask-file-loaded-hook #'eask--checker-existence) (add-hook 'eask-file-loaded-hook #'eask--checker-metadata) (defun eask--checker-string (name var) "Run checker for package's metadata. Argument NAME represent the name of that package's metadata. VAR is the actual variable we use to test validation." (unless (stringp var) (eask-error "✗ %s must be a string" name)) (when (string-empty-p var) (eask-warn "💡 %s cannot be an empty string" name))) ;; ;;; User customization (defcustom eask-file-loaded-hook nil "Hook runs after Easkfile is loaded." :type 'hook :group 'eask) (defcustom eask-before-command-hook nil "Hook runs before command is executed." :type 'hook :group 'eask) (defcustom eask-after-command-hook nil "Hook runs after command is executed." :type 'hook :group 'eask) (defcustom eask-on-error-hook nil "Hook runs when error is triggered." :type 'hook :group 'eask) (defcustom eask-on-warning-hook nil "Hook runs when warning is triggered." :type 'hook :group 'eask) (defcustom eask-dist-path "dist" "Default path where to place the package artifact." :type 'string :group 'eask) (defcustom eask-docs-path "docs/public/" "Default path where to place the documentation." :type 'string :group 'eask) (defcustom eask-recipe-path "recipes" "Name of default target directory for placing recipes." :type 'string :group 'eask) ;; ;;; Linter (defvar eask-lint-first-file-p nil "Set the flag to t after the first file is linted.") (defun eask-lint-first-newline () "Built-in linters will create extra newline, prevent that!" (when eask-lint-first-file-p (eask-msg "")) (setq eask-lint-first-file-p t)) ;; ;;; API (defvar eask-commands nil "List of defined commands.") (defmacro eask-defcommand (name &rest body) "Define an Eask command." (declare (doc-string 2) (indent 1)) (or name (error "Cannot define `%s` as a command" name)) (push name eask-commands) (setq eask-commands (delete-dups eask-commands)) `(defun ,name nil ,@body)) ;; ;;; Externals (eask-load "extern/ansi") (eask-load "extern/package") (eask-load "extern/package-build") ;;; _prepare.el ends here ================================================ FILE: lisp/clean/all.el ================================================ ;;; clean/all.el --- Do all cleaning tasks -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command that does all other cleaning tasks. ;; ;; $ eask clean all ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-no-cleaning-operation-p nil "Set to non-nil if there is no cleaning operation done.") (defconst eask-clean-all--tasks-total 6 "Count cleaning task.") (defvar eask-clean-all--tasks-count 0 "Count cleaning task.") (defvar eask-clean-all--tasks-cleaned 0 "Total cleaned tasks.") (defmacro eask--clean-section (title &rest body) "Print clean up TITLE and execute BODY." (declare (indent 1)) `(let (eask-no-cleaning-operation-p) (cl-incf eask-clean-all--tasks-count) (eask-with-progress (concat (format " - [%s/%s] " eask-clean-all--tasks-count eask-clean-all--tasks-total) (format "%s... " ,title)) (eask-with-verbosity 'debug ,@body) (if eask-no-cleaning-operation-p "skipped ✗" (cl-incf eask-clean-all--tasks-cleaned) "done ✓")))) (eask-start (eask-msg "Applying %s cleaning tasks..." eask-clean-all--tasks-total) (eask-msg "") (eask--clean-section (format "Cleaning %s" (ansi-green "workspace")) (eask-call "clean/workspace")) (eask--clean-section (format "Cleaning %s files" (ansi-green "byte-compile (.elc)")) (eask-call "clean/elc")) (eask--clean-section (format "Cleaning %s" (ansi-green "dist")) (eask-call "clean/dist")) (eask--clean-section (format "Cleaning %s file" (ansi-green "autoloads")) (eask-call "clean/autoloads")) (eask--clean-section (format "Cleaning %s" (ansi-green "pkg-file")) (eask-call "clean/pkg-file")) (eask--clean-section (format "Cleaning %s files" (ansi-green "log")) (eask-call "clean/log-file")) (eask-msg "") (eask-info "(Total of %s task%s cleaned, %s skipped)" eask-clean-all--tasks-cleaned (eask--sinr eask-clean-all--tasks-cleaned "" "s") (- eask-clean-all--tasks-count eask-clean-all--tasks-cleaned))) ;;; clean/all.el ends here ================================================ FILE: lisp/clean/autoloads.el ================================================ ;;; clean/autoloads.el --- Remove generated autoloads file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Remove generated autoloads file, ;; ;; $ eask clean autoloads ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let* ((name (eask-guess-package-name)) (autoloads-file (expand-file-name (concat name "-autoloads.el"))) (deleted (eask-delete-file autoloads-file))) (eask-msg "") (if deleted (eask-info "(Total of 1 file deleted)") (eask-info "(No autoloads file found in workspace)") (setq eask-no-cleaning-operation-p t)))) ;;; clean/autoloads.el ends here ================================================ FILE: lisp/clean/dist.el ================================================ ;;; clean/dist.el --- Delete dist subdirectory -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command that delete dist subdirectory, ;; ;; $ eask clean dist [destination] ;; ;; ;; Positional options: ;; ;; [destination] destination path/folder ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/package") ; load dist path (defun eask-clean-dist (path) "Clean up dist PATH." (let* ((name (eask-guess-package-name)) (version (eask-package-version)) (readme (expand-file-name (format "%s-readme.txt" name) path)) (entry (expand-file-name (format "%s-%s.entry" name version) path)) (packaged (eask-package-packaged-file)) (deleted 0) (delete-dir)) (when (eask-delete-file readme) (cl-incf deleted)) (when (eask-delete-file entry) (cl-incf deleted)) (when (eask-delete-file packaged) (cl-incf deleted)) (when (and (not (zerop deleted)) (eask-directory-empty-p path)) (eask-with-progress (format "The dist folder %s seems to be empty, delete it as well... " path) (ignore-errors (delete-directory path)) "done ✓") (setq delete-dir t)) (eask-msg "") (eask-info "(Total of %s file%s and %s directory deleted, %s skipped)" deleted (eask--sinr deleted "" "s") (if delete-dir "1" "0") (- 3 deleted)))) (eask-start (let* ((eask-dist-path (or (eask-args 0) eask-dist-path)) (eask-dist-path (expand-file-name eask-dist-path))) (if (file-directory-p eask-dist-path) (eask-clean-dist eask-dist-path) (eask-info "(No dist folder needs to be cleaned)" eask-dist-path) (setq eask-no-cleaning-operation-p t)))) ;;; clean/dist.el ends here ================================================ FILE: lisp/clean/elc.el ================================================ ;;; clean/elc.el --- Remove byte compiled files generated by eask compile -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Remove byte compiled files generated by eask compile ;; ;; $ eask clean elc ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (if-let* ((files (eask-package-elc-files))) (progn (mapc #'eask-delete-file files) (eask-msg "") (eask-info "(Total of %s .elc file%s deleted)" (length files) (eask--sinr files "" "s"))) (eask-info "(No .elc file found in workspace)") (setq eask-no-cleaning-operation-p t))) ;;; clean/elc.el ends here ================================================ FILE: lisp/clean/log-file.el ================================================ ;;; clean/log-file.el --- Remove all generated log files -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Remove all generated log files, ;; ;; $ eask clean log-file ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-clean-log-file (path) "Clean up .log PATH." (let ((log-files '("messages.log" "warnings.log" "backtrace.log" "compile-log.log")) (deleted 0) (delete-dir)) (dolist (log-file log-files) (when (eask-delete-file (expand-file-name log-file path)) (cl-incf deleted))) (when (and (not (zerop deleted)) (eask-directory-empty-p path)) (eask-with-progress (format "The dist folder %s seems to be empty, delete it as well... " path) (ignore-errors (delete-directory path)) "done ✓") (setq delete-dir t)) (eask-msg "") (eask-info "(Total of %s log file%s and %s directory deleted, %s skipped)" deleted (eask--sinr deleted "" "s") (if delete-dir "1" "0") (- (length log-files) deleted)))) (eask-start (let ((log-dir (expand-file-name eask-log-path eask-file-root))) (if (file-directory-p log-dir) (eask-clean-log-file log-dir) (eask-info "(No log file found in workspace)") (setq eask-no-cleaning-operation-p t)))) ;;; clean/log-file.el ends here ================================================ FILE: lisp/clean/pkg-file.el ================================================ ;;; clean/pkg-file.el --- Remove generated pkg-file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Remove generated pkg-file, ;; ;; $ eask clean pkg-file ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let* ((name (eask-guess-package-name)) (pkg-file (expand-file-name (concat name "-pkg.el"))) (deleted (eask-delete-file pkg-file))) (eask-msg "") (if deleted (eask-info "(Total of 1 file deleted)") (eask-info "(No pkg-file found in workspace)") (setq eask-no-cleaning-operation-p t)))) ;;; clean/pkg-file.el ends here ================================================ FILE: lisp/clean/workspace.el ================================================ ;;; clean/workspace.el --- Clean up .eask directory -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to clean up `.eask' in the working directory, ;; ;; $ eask clean workspace [-g] ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let ((target-dir (cond ((eask-global-p) eask-homedir) ((eask-config-p) user-emacs-directory) (t (file-name-directory (directory-file-name user-emacs-directory)))))) (unless eask--first-init-p (eask-msg "Deleting %s..." target-dir)) (ignore-errors (delete-directory target-dir t)) (if eask--first-init-p (progn (eask-info "(Workspace is already cleaned)") (setq eask-no-cleaning-operation-p t)) (eask-msg "") (eask-info "✓ (Workspace is now cleaned)" target-dir)))) ;;; clean/workspace.el ends here ================================================ FILE: lisp/core/analyze.el ================================================ ;;; core/analyze.el --- Run eask checker -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run Eask checker ;; ;; $ eask analyze [FILES..] ;; ;; ;; Positionals: ;; ;; [files..] specify Eask-files for checker to lint ;; ;; Optional arguments: ;; ;; --json Output lint result in JSON format ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; Plain Text (defvar eask-analyze--log nil) ;; JSON format (defvar eask-analyze--warnings nil) (defvar eask-analyze--errors nil) ;; Warning flag (defvar eask-analyze--warning-p nil) ;; Error flag (defvar eask-analyze--error-p nil) (defun eask-analyze--pretty-json (json) "Return pretty JSON." (with-temp-buffer (insert json) (json-pretty-print-buffer) (buffer-string))) (defun eask-analyze--load-buffer () "Return the current file loading session." (car (cl-remove-if-not (lambda (elm) (string-prefix-p " *load*-" (buffer-name elm))) (buffer-list)))) (defun eask-analyze--write-json-format (level msg) "Prepare log for JSON format. For arguments LEVEL and MSG, please see function `eask-analyze--write-log' for more information." (let* ((bounds (bounds-of-thing-at-point 'sexp)) (filename (or load-file-name eask-file)) (start (car bounds)) (end (cdr bounds)) (start-line (if load-file-name (line-number-at-pos start) 0)) (start-col (if load-file-name (eask--column-at-point start) 0)) (start-pos (if load-file-name start 0)) (end-line (if load-file-name (line-number-at-pos end) 0)) (end-col (if load-file-name (eask--column-at-point end) 0)) (end-pos (if load-file-name end 0)) (msg (ansi-color-filter-apply msg))) (push `((range . ((start . ((line . ,start-line) (col . ,start-col) (pos . ,start-pos))) (end . ((line . ,end-line) (col . ,end-col) (pos . ,end-pos))))) (filename . ,filename) (message . ,msg)) (cl-case level (`error eask-analyze--errors) (`warn eask-analyze--warnings))))) (defun eask-analyze--write-plain-text (level msg) "Prepare log for plain text format. For arguments LEVEL and MSG, please see function `eask-analyze--write-log' for more information." (let* ((level-string (cl-case level (`error "Error") (`warn "Warning"))) (log (format "%s:%s:%s %s: %s" (or load-file-name eask-file) (if load-file-name (line-number-at-pos) 0) (if load-file-name (current-column) 0) level-string msg))) (push (ansi-color-filter-apply log) eask-analyze--log))) (defun eask-analyze--write-log (level msg) "Write the log. Argument LEVEL and MSG are data from the debug log signal." (unless (string= " *temp*" (buffer-name)) ; avoid error from `package-file' directive (cl-case level (`error (setq eask-analyze--error-p t)) (`warn (setq eask-analyze--warning-p t))) (with-current-buffer (or (eask-analyze--load-buffer) (buffer-name)) (funcall (cond ((eask-json-p) #'eask-analyze--write-json-format) (t #'eask-analyze--write-plain-text)) level msg)))) (defun eask-analyze--file (files) "Lint list of Eask FILES." (let (checked-files content) ;; Linting (dolist (file files) (eask--silent-error (eask--save-load-eask-file file (push file checked-files) ;; also count files with errors in the total count (push file checked-files)))) ;; Print result (eask-msg "") (cond ((eask-json-p) ; JSON format ;; Fill content with result. (when (or eask-analyze--warnings eask-analyze--errors) (setq content (eask-analyze--pretty-json (json-encode `((warnings . ,eask-analyze--warnings) (errors . ,eask-analyze--errors)))))) ;; XXX: When printing the result, no color allow. (eask--with-no-color (eask-msg (or content "{}")))) (eask-analyze--log ; Plain text (setq content (with-temp-buffer (dolist (msg (reverse eask-analyze--log)) (insert msg "\n")) (buffer-string))) ;; XXX: When printing the result, no color allow. (eask--with-no-color (mapc #'eask-msg (reverse eask-analyze--log))))) (eask-info "(Checked %s file%s)" (length checked-files) (eask--sinr checked-files "" "s")) ;; Output file (when (and content (eask-output)) (write-region content nil (eask-output))))) ;; ;;; Program Entry ;; Preparation (add-hook 'eask-on-error-hook #'eask-analyze--write-log) (add-hook 'eask-on-warning-hook #'eask-analyze--write-log) (let* ((default-directory (cond ((eask-global-p) eask-homedir) ((eask-config-p) user-emacs-directory) (t default-directory))) (patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-expand-file-specs '("Eask*" "**/Eask*"))))) (cond ;; Files found, do the action! (files (eask-analyze--file files) (when (or eask-analyze--error-p ;; strict flag turns warnings into errors (and eask-analyze--warning-p (eask-strict-p))) (eask--exit 'failure))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No Eask-files have been checked)") (eask-help "core/analyze")))) ;;; core/analyze.el ends here ================================================ FILE: lisp/core/archives.el ================================================ ;;; core/archives.el --- List out all package archives -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to list out all package archives, ;; ;; $ eask archives ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-archive--length-name) (defvar eask-archive--length-url) (defvar eask-archive--length-priority) (defun eask-archive--print (archive) "Print the ARCHIVE." (let* ((name (car archive)) (url (cdr archive)) (priority (assoc name package-archive-priorities)) (priority (cdr priority))) (eask-println (concat " %-" eask-archive--length-name "s %-" eask-archive--length-url "s %-" eask-archive--length-priority "s") name (eask-2url url) (or priority 0)))) (defun eask-archive--print-alist (alist) "Print the archvie ALIST." (let* ((names (mapcar #'car alist)) (eask-archive--length-name (eask-2str (eask-seq-str-max names))) (urls (mapcar #'cdr alist)) (eask-archive--length-url (eask-2str (eask-seq-str-max urls))) (priorities (mapcar #'cdr package-archive-priorities)) (eask-archive--length-priority (eask-2str (eask-seq-str-max priorities)))) (mapc #'eask-archive--print alist))) (eask-start (cond ((eask-all-p) (eask-info "Available archives:") (eask-msg "") (eask-archive--print-alist eask-source-mapping) (eask-info "(Total of %s archive%s available)" (length eask-source-mapping) (eask--sinr eask-source-mapping "" "s"))) (package-archives (eask-info "Archives in use:") (eask-msg "") (eask-archive--print-alist package-archives) (eask-info "(Total of %s archive%s listed)" (length package-archives) (eask--sinr package-archives "" "s"))) (t (eask-info "(No archive has been selected)")))) ;;; core/archives.el ends here ================================================ FILE: lisp/core/bump.el ================================================ ;;; core/bump.el --- Bump version for your project -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to bump version, ;; ;; $ eask bump ;; ;; ;; Positionals: ;; ;; [levels..] version level to bump; accept `major', `minor' or `patch' ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-bump--version (version index) "Bump VERSION with INDEX." (let ((lst (if (stringp version) (version-to-list version) version))) (setf (nth index lst) (cl-incf (nth index lst))) (mapconcat #'eask-2str lst version-separator))) (defun eask-bump--version-major-version (version) "Bump VERSION major level." (eask-bump--version version 0)) (defun eask-bump--version-minor-version (version) "Bump VERSION minor level." (eask-bump--version version 1)) (defun eask-bump--version-patch-level (version) "Bump VERSION patch level." (eask-bump--version version 2)) (eask-start (let ((package-file (or eask-package-file (eask-guess-entry-point))) (levels (eask-args)) (desc) (version) (tasks (if eask-file 2 1))) (unless eask-package-file (eask-info "💡 Detect package file `%s'..." package-file)) (cond ((and (not (member "major" levels)) (not (member "minor" levels)) (not (member "patch" levels))) (eask-help "core/bump")) ((not (file-exists-p package-file)) (eask-info "(No package file found)")) (t (with-current-buffer (find-file package-file) (setq desc (package-buffer-info))) (setq version (package-desc-version desc)) (when (member "major" levels) (setq version (eask-bump--version-major-version version))) (when (member "minor" levels) (setq version (eask-bump--version-minor-version version))) (when (member "patch" levels) (setq version (eask-bump--version-patch-level version))) (eask-msg "New version: %s" version) (let (success) (eask-with-progress (format " - [1/%s] Bump version for package-file (%s)... " tasks (eask-root-del package-file)) (with-current-buffer (find-file package-file) (goto-char (point-min)) (cond ((re-search-forward ";;[ \t]*Version:[ \t]*" nil t) (delete-region (point) (line-end-position)) (insert version) (setq success t) (save-buffer)) (t (eask-error "Failed to bump version to package file; invalid search string: %s" package-file)))) (if success "done ✓" "skipped ✗"))) (when eask-file (let (success) (eask-with-progress (format " - [1/%s] Bump version for Eask-file (%s)... " tasks (eask-root-del eask-file)) (with-current-buffer (find-file eask-file) (goto-char (point-min)) (cond ((re-search-forward "(package[ \t\r\n\"]*" nil t) (forward-char -1) (forward-thing 'sexp) (forward-thing 'sexp) (forward-sexp -1) (delete-region (1+ (point)) (save-excursion (forward-thing 'sexp) (1- (point)))) (forward-char 1) (insert version) (setq success t) (save-buffer)) (t (eask-error "Failed to bump version to Eask-file; invalid search string: %s" eask-file)))) (if success "done ✓" "skipped ✗")))))))) ;;; core/bump.el ends here ================================================ FILE: lisp/core/cat.el ================================================ ;;; core/cat.el --- View filename(s) -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to view filename(s) ;; ;; $ eask cat ;; ;; ;; Positionals: ;; ;; filename(s) to view ;; ;; Optional arguments: ;; ;; --number, -n view with line numbers ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'e2ansi) (eask-msg "") ;; Start the task (require 'e2ansi) (if-let* ((patterns (eask-args)) (filenames (eask-expand-file-specs patterns))) (dolist (filename filenames) (eask-info "[+] %s" filename) (with-current-buffer (find-file filename) (ignore-errors (font-lock-ensure)) (goto-char (point-min)) (let* ((max-line (save-excursion (line-number-at-pos (point-max)))) (max-line (eask-2str max-line)) (offset (eask-2str (length max-line))) (line-no 1)) (while (not (eobp)) (let* ((line (buffer-substring (line-beginning-position) (line-end-position))) (line (if ansi-inhibit-ansi line (e2ansi-string-to-ansi line)))) (if (eask-number-p) (eask-println (concat "%" offset "d %s") line-no line) (eask-println "%s" line))) (forward-line 1) (cl-incf line-no))))) (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " ")))) ;;; core/cat.el ends here ================================================ FILE: lisp/core/compile.el ================================================ ;;; core/compile.el --- Byte-compile `.el' files -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Byte-compile `.el' files, ;; ;; $ eask compile [names..] ;; ;; ;; Positionals: ;; ;; [names..] specify files to byte-compile ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Handle options (eask-add-hook '( eask-before-command-hook) (when (eask-strict-p) (setq byte-compile-error-on-warn t)) (when (eask-reach-verbosity-p 'debug) (setq byte-compile-verbose t))) ;; ;;; Core (require 'bytecomp) ;; XXX: The function `byte-compile-warn' was last modified in 2015; ;; I'll say it's safe to override this function. (advice-add 'byte-compile-warn :override (lambda (format &rest args) (setq format (apply #'format-message format args)) (byte-compile-log-warning format t (if byte-compile-error-on-warn :error :warning)))) (defun eask-compile--print-log () "Print `*Compile-Log*' buffer." (when (get-buffer byte-compile-log-buffer) (with-current-buffer byte-compile-log-buffer (if (and (eask-clean-p) (eask-strict-p)) (eask-error (buffer-string)) ; Exit with error code! (eask-print-log-buffer)) (eask-msg "")))) (defun eask-compile--byte-compile-file-external-content (filename cmd) "Extract result after executing byte-compile the FILENAME. The CMD is the command to start a new Emacs session." (with-temp-buffer (insert (shell-command-to-string cmd)) (goto-char (point-min)) (search-forward filename nil t) (re-search-forward "[ \t\r\n]" nil t) (let ((line (string-trim (thing-at-point 'line)))) (if (and (string-prefix-p "Compiling " line) (or (string-match-p "... skipped" line) (string-match-p "... done" line))) (delete-region (point-min) (line-end-position 1)) (delete-region (point-min) (point)))) (when (search-forward "(Total of " nil t) (goto-char (point-max)) (delete-region (line-beginning-position -1) (point-max))) (string-trim (buffer-string)))) (defun eask-compile--byte-compile-file-external (filename) "Byte compile FILENAME with clean environment by opening a new Emacs session." (let* ((cmd (split-string eask-invocation "\n" t)) (cmd (format "\"%s\""(mapconcat #'identity cmd "\" \""))) (args (eask-args)) (argv (cl-remove-if (lambda (arg) (or (string= "--clean" arg) ; prevent infinite call (member arg args))) ; remove repeated arguments (eask-argv-out))) (args (append `(,(eask-command) ,(concat "\"" filename "\"")) argv)) (args (mapconcat #'identity args " ")) (cmd (concat cmd " " args)) (content (eask-compile--byte-compile-file-external-content filename cmd))) (if (string-empty-p content) t ; no error, good! (with-current-buffer (get-buffer-create byte-compile-log-buffer) (insert content))))) (defun eask-compile--byte-compile-file (filename) "Byte compile FILENAME." (eask-ignore-errors ;; *Compile-Log* does not kill itself. Make sure it's clean before we do ;; next byte-compile task. (ignore-errors (kill-buffer byte-compile-log-buffer)) (let* ((filename (expand-file-name filename)) (result)) (eask-with-progress (unless byte-compile-verbose (format "Compiling %s... " filename)) (eask-with-verbosity 'debug (setq result (if (eask-clean-p) (eask-compile--byte-compile-file-external filename) (byte-compile-file filename)) result (eq result t))) (unless byte-compile-verbose (if result "done ✓" "skipped ✗"))) (eask-compile--print-log) result))) (defun eask-compile--files (files) "Compile sequence of FILES." (let* ((compiled (cl-remove-if-not #'eask-compile--byte-compile-file files)) (compiled (length compiled)) (skipped (- (length files) compiled))) ;; XXX: Avoid last newline from the log buffer! (unless (get-buffer byte-compile-log-buffer) (eask-msg "")) (eask-info "(Total of %s file%s compiled, %s skipped)" compiled (eask--sinr compiled "" "s") skipped))) (eask-start (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (eask-compile--files files)) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been compiled)") (eask-help "core/compile"))))) ;;; core/compile.el ends here ================================================ FILE: lisp/core/concat.el ================================================ ;;; core/concat.el --- Byte compile all Emacs Lisp files in the package -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Byte concatenate all Emacs Lisp files into one file ;; ;; $ eask concat [names..] ;; ;; ;; Positionals: ;; ;; [names..] specify files to concatenate ;; ;; Action options: ;; ;; [destination] optional output destination ;; [output] optional output filename ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let* ((name (eask-guess-package-name)) (patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files))) (eask-dist-path (or (eask-dest) eask-dist-path)) (eask-dist-path (expand-file-name eask-dist-path)) (target-file (concat name ".built.el")) (target-filename (or (and (eask-output) (expand-file-name (eask-output))) (expand-file-name target-file eask-dist-path)))) (cond ;; Files found, do the action! (files (eask-debug "Destination path in %s" eask-dist-path) (ignore-errors (make-directory eask-dist-path t)) (eask-info "Prepare to concatenate files %s..." target-filename) (write-region "" nil target-filename) (eask-with-verbosity 'log (with-temp-buffer (eask-progress-seq " - Visiting" files "appended! ✓" #'insert-file-contents) (write-region (buffer-string) nil target-filename) (eask-msg "") (eask-info "Done. (Wrote file in %s)" target-filename)))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been concatenated)") (eask-help "core/concat"))))) ;;; core/concat.el ends here ================================================ FILE: lisp/core/emacs.el ================================================ ;;; core/emacs.el --- Execute emacs with the appropriate environment -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Execute Emacs with the appropriate environment ;; ;; $ eask emacs [args..] ;; ;; ;; Positionals: ;; ;; [args..] arguments feed into Emacs executable ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-l" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Below is copied from `eask-start' macro (eask--handle-global-options) (setq user-emacs-directory (expand-file-name (concat ".eask/" emacs-version "/")) package-user-dir (expand-file-name "elpa" user-emacs-directory) early-init-file (locate-user-emacs-file "early-init.el") eask-dot-emacs-file (locate-user-emacs-file ".emacs") user-init-file (locate-user-emacs-file "init.el") custom-file (locate-user-emacs-file "custom.el")) (eask-file-try-load default-directory) ; Try respect the Eask file settings. (package-activate-all) (ignore-errors (make-directory package-user-dir t)) (eask--silent (eask-setup-paths)) ;; NOTE: If you modified this execution, make sure you modified the function ;; `eask--load-config' as well! (let ((inhibit-config (eask-quick-p))) (unless inhibit-config (when (version<= "27" emacs-version) (load early-init-file t t)) (load eask-dot-emacs-file t t) (load user-init-file t t))) ;;; core/emacs.el ends here ================================================ FILE: lisp/core/eval.el ================================================ ;;; core/eval.el --- Evaluate lisp form with a proper PATH -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Evaluate Lisp form with a proper PATH, ;; ;; $ eask eval [form] ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/exec") (eask-start (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask-setup-paths) (if-let* ((name (eask-argv 0))) (eask-with-progress (ansi-green "Exporting environment variables... ") (eask-exec-export-env) (ansi-green "done ✓")) (eask-info "(No expression found)") (eask-help "core/eval"))) ;;; core/eval.el ends here ================================================ FILE: lisp/core/exec-path.el ================================================ ;;; core/exec-path.el --- Print the PATH (exec-path) from workspace -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Print the PATH (exec-path) from workspace ;; ;; $ eask path ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/load-path") (defun eask-exec-path--print (path) "Print out the PATH." (eask-println "%s" path)) (eask-start (eask-pkg-init) (let* ((patterns (eask-args)) (exec-path (if patterns (cl-remove-if-not #'eask-load-path--filter exec-path) exec-path))) (eask-msg "") (mapc #'eask-exec-path--print exec-path) (if (zerop (length exec-path)) (eask-info "(No exec-path found)") (eask-info "(Total of %s `exec-path` printed)" (length exec-path))))) ;;; core/exec-path.el ends here ================================================ FILE: lisp/core/exec.el ================================================ ;;; core/exec.el --- Execute command with correct PATH set up -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Execute command with correct load-path set up ;; ;; $ eask exec [args..] ;; ;; ;; Positionals: ;; ;; [args..] execute command with correct PATH set up ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defconst eask-exec--exec-path-file (expand-file-name "exec-path" eask-homedir) "Target file to export the variable `exec-path'.") (defconst eask-exec--load-path-file (expand-file-name "load-path" eask-homedir) "Target file to export the variable `load-path'.") (defun eask-exec-export-env () "Export environments." (ignore-errors (delete-file eask-exec--exec-path-file)) (ignore-errors (delete-file eask-exec--load-path-file)) (ignore-errors (make-directory eask-homedir t)) ; generate dir `~/.eask/' (write-region (getenv "PATH") nil eask-exec--exec-path-file) (write-region (getenv "EMACSLOADPATH") nil eask-exec--load-path-file)) (eask-start (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x ;; XXX: This is the hack by adding all `bin' folders from local elpa. (eask-setup-paths) (if (eask-argv 1) (eask-with-progress (ansi-green "Exporting environment variables... ") (eask-exec-export-env) (ansi-green "done ✓")) (eask-info "(No exeuction output)") (eask-help "core/exec"))) ;;; core/exec.el ends here ================================================ FILE: lisp/core/files.el ================================================ ;;; core/files.el --- Print the list of all package files -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Print the list of all package files ;; ;; $ eask files ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-files--print-filename (filename) "Print out the FILENAME." (eask-println "%s" filename)) (eask-start (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-files)))) (mapc #'eask-files--print-filename files) (if (zerop (length files)) (eask-info "(No package files found)") (eask-info "(Total of %s item%s listed)" (length files) (eask--sinr files "" "s"))))) ;;; core/files.el ends here ================================================ FILE: lisp/core/info.el ================================================ ;;; core/info.el --- Display information about the current package -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Display information about the current package ;; ;; $ eask info ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-info--max-offset 0 "The maximum offset to print the info.") (defun eask-info--print-deps (title dependencies) "Print DEPENDENCIES with TITLE identifier." (when dependencies (eask-println "") (eask-println title) (let* ((names (mapcar (lambda (dep) (ansi-green (eask-2str (car dep)))) dependencies)) (offset (eask-seq-str-max names))) (setq offset (if (eask-no-color-p) offset ;; XXX: I'm not sure why we need to plus 2 here. ;; My guess is regarding the ansi escape characters. (+ offset 2)) eask-info--max-offset (max offset eask-info--max-offset) offset (eask-2str eask-info--max-offset)) (dolist (dep dependencies) (let* ((target-version (cdr dep)) (target-version (cond ((memq :file dep) "file") ((memq :vc dep) "vc") ((memq :try dep) "try") ((= (length target-version) 1) (or (nth 0 target-version) ; verison number "archive")) (t "recipe")))) (eask-println (concat " %-" offset "s (%s) %s") (ansi-green (eask-2str (car dep))) (ansi-yellow target-version) ;; Debug print the recipe format. (if (eask-reach-verbosity-p 'debug) (ansi-blue (eask-2str dep)) ""))))))) (eask-start (if eask-package (progn (eask-println "%s (%s) | deps: %s | devDeps: %s" (ansi-green (eask-package-name)) (ansi-yellow (eask-package-version)) (ansi-cyan (eask-2str (length eask-depends-on))) (ansi-cyan (eask-2str (length eask-depends-on-dev)))) (unless (string-empty-p (eask-package-description)) (eask-println (eask-package-description))) (when-let* ((url (or (eask-package-desc-url) eask-website-url)) ((not (string-empty-p url)))) (eask-println (ansi-blue url))) (when-let* ((keywords (or (eask-package-desc-keywords) eask-keywords)) (keywords (mapcar (lambda (keyword) (ansi-cyan keyword)) keywords))) (eask-println "") (eask-println "keywords: %s" (string-join keywords ", "))) (eask-println "") (when eask-package-file (eask-println "entry: %s" (ansi-cyan (eask-root-del eask-package-file)))) (eask-println "kind: %s" (ansi-cyan (if (eask-package-multi-p) "tar" "single"))) (eask-println "") (eask-println "dist") (eask-println ".total-files: %s" (ansi-magenta (eask-2str (length (eask-package-files))))) (eask-println ".unpacked-size: %s" (ansi-magenta (eask-unpacked-size))) (eask-info--print-deps "dependencies:" eask-depends-on) (eask-info--print-deps "devDependencies:" eask-depends-on-dev)) (eask-info "(Eask file has no package information)") (eask-help "core/info"))) ;;; core/info.el ends here ================================================ FILE: lisp/core/init.el ================================================ ;;; core/init.el --- Initialize project to use Eask -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Initialize project to use Eask ;; ;; $ eask init ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-init--check-filename (name) "Return non-nil if NAME is a valid Eask-file." (when-let* ((name (file-name-nondirectory (directory-file-name name))) (prefix (cond ((string-prefix-p "Easkfile" name) "Easkfile") ((string-prefix-p "Eask" name) "Eask")))) (let ((suffix (car (split-string name prefix t)))) (or (null suffix) (string-match-p "^[.][.0-9]*$" suffix))))) (eask-start (let* ((dir (eask-working-directory)) (files (eask--all-files dir)) (new-name (expand-file-name "Eask" dir)) (base-name) (invalid-name) (continue t)) (when (and files (setq continue (yes-or-no-p (concat "Eask-file already exist,\n\n " (mapconcat #'identity files "\n ") "\n\nContinue the creation? ")))) (while (or (file-exists-p new-name) invalid-name) (setq new-name (read-file-name (format (concat (if invalid-name "[?] Invalid filename `%s', " "[?] Filename `%s' already taken, ") "try another one: ") (file-name-nondirectory (directory-file-name new-name))) dir nil nil nil #'eask-init--check-filename) base-name (file-name-nondirectory (directory-file-name new-name)) invalid-name (not (eask-init--check-filename base-name))))) (when continue (eask-println "This utility will walk you through creating an Eask file. It only covers the most common items, and tries to guess sensible defaults. See `eask init --help` for definitive documentation on these fields and exactly what they do. Use `eask install ` afterwards to install a package and save it as a dependency in the Eask file. Press ^C at any time to quit.") ;; Starting Eask-file creation! (let* ((project-dir (file-name-nondirectory (directory-file-name dir))) (project-name (eask-guess-package-name project-dir)) (package-name (read-string (format "package name: (%s) " project-name) nil nil project-name)) (version (read-string "version: (1.0.0) " nil nil "1.0.0")) (description (read-string "description: ")) (guess-entry-point (format "%s.el" project-name)) (entry-point (read-string (format "entry point: (%s) " guess-entry-point) nil nil guess-entry-point)) (emacs-version (read-string "emacs version: (26.1) " nil nil "26.1")) (website (read-string "website: ")) (keywords (read-string "keywords: ")) (keywords (if (string-match-p "," keywords) (split-string keywords ",[ \t\n]*" t "[ ]+") (split-string keywords "[ \t\n]+" t "[ ]+"))) (keywords (mapconcat (lambda (s) (format "%S" s)) keywords " ")) (content (format ";; -*- mode: eask; lexical-binding: t -*- (package \"%s\" \"%s\" \"%s\") (website-url \"%s\") (keywords %s) (package-file \"%s\") (script \"test\" \"echo \\\"Error: no test specified\\\" && exit 1\") (source \"gnu\") (depends-on \"emacs\" \"%s\") " package-name version description website keywords entry-point emacs-version)) (prompt (format "About to write to %s:\n\n%s\n\nIs this Okay? " new-name content))) (when (yes-or-no-p prompt) (write-region content nil new-name) (find-file new-name)))))) ;;; core/init.el ends here ================================================ FILE: lisp/core/install-deps.el ================================================ ;;; core/install-deps.el --- Automatically install package dependencies -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to install package dependencies ;; ;; $ eask install-deps ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start ;; XXX: You must refresh content before you install the package, ;; see https://github.com/ericdallo/jet.el/issues/1 (eask-pkg-init) (if (eask-dependencies) (progn (when (and (eask-dev-p) (not eask-depends-on-dev)) (eask-warn "No development dependencies found in your Eask file; but continue to install package dependencies")) (eask-install-dependencies)) (eask-info "(No dependencies found in your Eask file)") (eask-help "core/install-deps"))) ;;; core/install-deps.el ends here ================================================ FILE: lisp/core/install-file.el ================================================ ;;; core/install-file.el --- Install packages from files, .tar files, or directories -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to install packages from files, .tar files, or directories ;; ;; $ eask install-file [files..] ;; ;; ;; Positionals: ;; ;; [files..] files to install as packages; it will install through the ;; function `package-install-file' ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/install") (defun eask-install-file--get-package-name (path) "Get the package name from PATH, which is a file, directory or archive." (let ((path (expand-file-name path))) (cond ((not (file-exists-p path)) (eask-error "✗ File does not exist in `%s`" path)) ;; TAR file ((string-match-p "[.]+tar[.]*" path) ;; Note this can throw strange errors if ;; ;; - there is no -pkg.el in the tar file ;; - the tar file was built in a folder with a different name ;; ;; TAR files created with eask package are fine. (require 'tar-mode) (let ((pkg-desc (with-temp-buffer (insert-file-contents-literally path) (tar-mode) (ignore-errors (package-tar-file-info))))) (unless pkg-desc ;; `package-dir-info' will return nil if there is no `-pkg.el' ;; and no `.el' files at path (eask-error "✗ No package in `%s`" path)) (package-desc-name pkg-desc))) ;; .el file or directory (t ;; Note `package-dir-info' doesn't work outside of dired mode! (let ((pkg-desc (with-temp-buffer (dired path) (ignore-errors (package-dir-info))))) (unless pkg-desc ;; `package-dir-info' will return nil if there is no `-pkg.el' ;; and no `.el' files at path (eask-error "✗ No package in `%s`" path)) (package-desc-name pkg-desc)))))) (defun eask-install-file--packages (files) "The file install packages with FILES." (let* ((deps (mapcar (lambda (file) (list (eask-install-file--get-package-name file) file)) files)) (names (mapcar #'car deps)) (len (length deps)) (s (eask--sinr len "" "s")) (not-installed (eask-install--not-installed names)) (installed (length not-installed)) (skipped (- len installed))) (eask-log "Installing %s specified file package%s..." len s) (eask-msg "") (eask--package-mapc (lambda (dep &rest _) (apply #'eask-package-install-file dep)) deps) (eask-msg "") (eask-info "(Total of %s file package%s installed, %s skipped)" installed s skipped))) (eask-start (eask-pkg-init) (if-let* ((files (eask-args))) ;; If package [files..] are specified, we try to install it (eask-install-file--packages files) ;; Otherwise, report error. (eask-info "(No file packages have been installed)") (eask-help "core/install-file"))) ;;; core/install-file.el ends here ================================================ FILE: lisp/core/install-vc.el ================================================ ;;; core/install-vc.el --- Install packages directly from the version control -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to install packages directly from the version control ;; ;; $ eask install-vc [specs..] ;; ;; ;; Positionals: ;; ;; [specs..] specs to install as packages; it will install through the ;; function `package-vc-install' ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/install") ;; ;;; Flags (eask-command-check "29.1") ;; ;;; Core (defun eask-install-vc--guess-name (file) "Guess the package name of the install FILE." (file-name-sans-extension (file-name-nondirectory (directory-file-name file)))) (defun eask-install-vc--split-sepcs (specs) "Split the SPECS and return a list of specification." (let ((new-specs) (current-spec)) (dolist (spec specs) ;; Detect new specification. (cond ((ffap-url-p spec) (push (reverse current-spec) new-specs) ;; We're using the push, so the order is reversed. (setq current-spec (list spec (eask-install-vc--guess-name spec)))) (t (push spec current-spec)))) ;; Push thes rest of the specification. (push (reverse current-spec) new-specs) (cl-remove-if #'null (reverse new-specs)))) (defun eask-install-vc--packages (specs) "The vc install packages with SPECS." (let* ((deps (eask-install-vc--split-sepcs specs)) (names (mapcar #'car deps)) (len (length deps)) (s (eask--sinr len "" "s")) (not-installed (eask-install--not-installed names)) (installed (length not-installed)) (skipped (- len installed))) (eask-log "Installing %s specified vc package%s..." len s) (eask-msg "") (eask--package-mapc (lambda (dep &rest _) (let ((name (car dep)) (spec (cdr dep))) (eask-package-vc-install name spec))) deps) (eask-msg "") (eask-info "(Total of %s vc package%s installed, %s skipped)" installed s skipped))) (eask-start (eask-pkg-init) (if-let* ((specs (eask-args))) ;; If package [specs..] are specified, we try to install it (eask-install-vc--packages specs) ;; Otherwise, report error. (eask-info "(No vc packages have been installed)") (eask-help "core/install-vc"))) ;;; core/install-vc.el ends here ================================================ FILE: lisp/core/install.el ================================================ ;;; core/install.el --- Install packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to install Emacs packages, ;; ;; $ eask install [names..] ;; ;; ;; Positionals: ;; ;; [names..] name of the package to install; else we try to install ;; package from current directory by calling function ;; `package-install-file' ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/package") ; load dist path (defun eask-install--not-installed (names) "Return a list of not installed packages' NAMES." (cl-remove-if-not (lambda (name) (or (eask-force-p) (not (package-installed-p (eask-intern name))))) names)) (defun eask-install--packages (names) "Install packages with their NAMES." (let* ((names (mapcar #'eask-intern names)) (len (length names)) (s (eask--sinr len "" "s")) (not-installed (eask-install--not-installed names)) (installed (length not-installed)) (skipped (- len installed))) (eask-log "Installing %s specified package%s..." len s) (eask-msg "") (eask--package-mapc #'eask-package-install names) (eask-msg "") (eask-info "(Total of %s package%s installed, %s skipped)" installed s skipped))) ;; NOTE: This is copied from `eldev'! Great thanks! ;; ;; XXX: remove this after we drop 28.x (defun eask-install--file (file) "Old compatible version of function `package-install-file'. For argument FILE, please see function `package-install-file' for the details." ;; Workaround: `package-install-file' fails when FILE is .el and contains CRLF EOLs: ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=48137 (if (not (string-match "\\.el\\'" file)) (package-install-file file) ;; load package file and check if it contains CRLFs (with-temp-buffer (insert-file-contents-literally file) (goto-char (point-min)) (if (not (search-forward "\r\n" nil t)) (package-install-file file) ; no CRLF ;; CRLF found (let* ((nondir (file-name-nondirectory file)) (temp-dir (make-temp-file "eask" t)) (temp-file (expand-file-name nondir temp-dir))) (unwind-protect ;; replace CRLFs with LFs and write to new temporary ;; package file (progn (replace-match "\n" nil t) (while (search-forward "\r\n" nil t) (replace-match "\n" nil t)) (write-region (point-min) (point-max) temp-file) (package-install-file temp-file)) ;; clean up temporary file (delete-directory temp-dir t))))))) (eask-start ;; XXX: You must refresh content before you install the package, ;; see https://github.com/ericdallo/jet.el/issues/1 (eask-pkg-init) (if-let* ((names (eask-args))) ;; If package [name..] are specified, we try to install it (eask-install--packages names) ;; Else we try to install package from the working directory (eask-install-dependencies) (let* ((name (eask-guess-package-name)) (packaged (eask-package-packaged-file)) (packaged (when (file-exists-p packaged) packaged)) (target (or packaged eask-package-file))) (eask-log "Searching for artifact to install...") (if packaged (eask-info "💡 Found artifact in %s" target) (eask-info "💡 Missing artifact, install directly from %s" target)) (if target (progn (add-to-list 'load-path (expand-file-name (eask-package-packaged-name) package-user-dir)) ;; XXX: Use regular `package-install-file' function after we drop 28.x (eask-install--file target) (eask-msg "") (eask-info "(Installed in %s)" (file-name-directory (locate-library name)))) (eask-info "(No files have been intalled)") (eask-help "core/install"))))) ;;; core/install.el ends here ================================================ FILE: lisp/core/keywords.el ================================================ ;;; core/keywords.el --- List all available keywords -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; List available keywords that can be used in the header section, ;; ;; $ eask keywords ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (require 'finder) (eask-start (let* ((keywords (mapcar #'car finder-known-keywords)) (offset (eask-seq-str-max keywords)) (fmt (concat " %-" (eask-2str offset) "s %s"))) (dolist (keyword keywords) (eask-println fmt keyword (cdr (assq keyword finder-known-keywords)))) (eask-info "(Total of %s keywords listed)" (length keywords)))) ;;; core/keywords.el ends here ================================================ FILE: lisp/core/list.el ================================================ ;;; core/list.el --- List all installed packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to list out installed Emacs packages, ;; ;; $ eask list ;; ;; ;; Action options: ;; ;; [--depth] dependency level to print ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-list--pkg-name-offset nil) (defvar eask-list--pkg-version-offset nil) (defvar eask-list--pkg-archive-offset nil) (defun eask-list--format-s (offset) "Format OFFSET." (concat " %-" (number-to-string offset) "s ")) (defun eask-list--align (depth &optional rest) "Format string to align starting from the version number. Argument DEPTH is used to calculate the indentation. REST is a list of string for string concatenation." (let ((prefix (if (= depth 0) "[+]" "[+]"))) (concat (spaces-string (* depth 2)) ; indent for depth " " prefix (eask-list--format-s (- eask-list--pkg-name-offset (* depth 2))) (eask-list--format-s eask-list--pkg-version-offset) (eask-list--format-s eask-list--pkg-archive-offset) rest))) (defun eask-list--print-pkg (name depth max-depth pkg-alist) "Print NAME package information. Argument DEPTH is the current dependency nested level. MAX-DEPTH is the limitation, so we don't go beyond this deepness. PKG-ALIST is the archive contents." (when-let* ((pkg (assq name pkg-alist)) (desc (cadr pkg)) (name (package-desc-name desc)) (version (package-desc-version desc)) (version (package-version-join version)) (archive (or (package-desc-archive desc) "")) (summary (package-desc-summary desc))) (if (= depth 0) (eask-msg (eask-list--align depth " %-80s") name version archive summary) (eask-msg (eask-list--align depth) name "" "" "")) (when-let* ((reqs (package-desc-reqs desc)) ((< depth max-depth))) (dolist (req reqs) (eask-list--print-pkg (car req) (1+ depth) max-depth pkg-alist))))) (defun eask-list--version-list (pkg-alist) "Return a list of versions. PKG-ALIST is the archive contents." (mapcar (lambda (elm) (package-version-join (package-desc-version (cadr elm)))) pkg-alist)) (defun eask-list--archive-list (pkg-alist) "Return list of archives. PKG-ALIST is the archive contents." (mapcar (lambda (elm) (or (package-desc-archive (cadr elm)) "")) pkg-alist)) (defun eask-list (list pkg-alist &optional depth) "List packages. Argument LIST is the list of packages we want to list. PKG-ALIST is the archive contents we want to retrieve package's metadate from. Optional argument DEPTH is the deepness of the dependency nested level we want to go." (let* ((eask-list--pkg-name-offset (eask-seq-str-max list)) (version-list (eask-list--version-list pkg-alist)) (eask-list--pkg-version-offset (eask-seq-str-max version-list)) (archive-list (eask-list--archive-list pkg-alist)) (eask-list--pkg-archive-offset (eask-seq-str-max archive-list))) (dolist (name list) (eask-list--print-pkg name 0 (or depth (eask-depth) 999) pkg-alist)))) (eask-start (cond ((eask-all-p) (eask-pkg-init) ; XXX: You must have this! (let ((pkg-list (reverse (mapcar #'car package-archive-contents)))) (eask-list pkg-list package-archive-contents)) (eask-msg "") (eask-info "(Total of %s package%s available)" (length package-archive-contents) (eask--sinr package-archive-contents "" "s"))) (t (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask-list package-activated-list package-alist) (unless (zerop (length package-activated-list)) (eask-msg "")) (eask-info "(Total of %s package%s installed)" (length package-activated-list) (eask--sinr package-activated-list "" "s"))))) ;;; core/list.el ends here ================================================ FILE: lisp/core/load-path.el ================================================ ;;; core/load-path.el --- Print the load-path from workspace -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Print the load-path from workspace ;; ;; $ eask load-path ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-load-path--print (path) "Print out the PATH." (eask-println "%s" path)) (defun eask-load-path--filter (path) "Filter the PATH out by search regex." (cl-some (lambda (regex) (string-match-p regex path)) (eask-args))) (eask-start (eask-pkg-init) (let* ((patterns (eask-args)) (load-path (if patterns (cl-remove-if-not #'eask-load-path--filter load-path) load-path))) (eask-msg "") (mapc #'eask-load-path--print load-path) (if (zerop (length load-path)) (eask-info "(No load-path found)") (eask-info "(Total of %s `load-path` printed)" (length load-path))))) ;;; core/load-path.el ends here ================================================ FILE: lisp/core/load.el ================================================ ;;; core/load.el --- load files -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to load files (accept multiple) ;; ;; $ eask load ;; ;; ;; Positionals: ;; ;; [files..] specify files to load ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (if-let* ((files (eask-expand-file-specs (eask-args)))) (mapc #'load-file files) (eask-info "(Nothing to load.)"))) ;;; core/load.el ends here ================================================ FILE: lisp/core/loc.el ================================================ ;;; core/loc.el --- Print LOC information -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to print loc (accept multiple) ;; ;; $ eask loc ;; ;; ;; Positionals: ;; ;; [files..] specify files to print LOC information ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Flags (eask-command-check "28.1") ;; ;;; Core (defvar eask-loc--lines 0) (defvar eask-loc--chars 0) (defun eask-loc--file (file) "Insert LOC information for FILE." (unless (file-directory-p file) (let ((lines) (chars)) (with-temp-buffer (insert-file-contents file) (setq lines (line-number-at-pos (point-max)) chars (point-max))) (cl-incf eask-loc--lines lines) (cl-incf eask-loc--chars chars) (insert (format "| %s | %s | %s |\n" file lines chars))))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'markdown-mode) (require 'markdown-mode) ;; Start LOC (if-let* ((files (or (eask-expand-file-specs (eask-args)) (eask-package-files))) (eask-output (get-buffer-create "*eask-output*"))) (with-current-buffer eask-output (erase-buffer) (progn ; Print header (insert (format "|---|---|---|\n")) (insert (format "| File | Lines | Characters |\n")) (insert (format "|---|---|---|\n"))) (eask-with-progress "Retrieving LOC information... " (mapc #'eask-loc--file files) "done ✓") (progn ; Print total (insert (format "|---|---|---|\n")) (insert (format "| Total | %s | %s |\n" eask-loc--lines eask-loc--chars))) (progn ; Organize (goto-char (point-min)) (markdown-table-align)) (eask-println "") (eask-print (buffer-string)) (eask-println "")) (eask-info "(No LOC information to print.)"))) ;;; core/loc.el ends here ================================================ FILE: lisp/core/outdated.el ================================================ ;;; core/outdated.el --- Show all outdated dependencies -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to show all outdated dependencies, ;; ;; $ eask outdated ;; ;; ;; Action options: ;; ;; [--depth] dependency level to print ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/upgrade") (eask-load "core/list") (eask-start (eask-pkg-init) (if-let* ((upgrades (eask-package--upgrades)) (pkg-list (mapcar #'package-desc-name upgrades))) (progn (when (eask-local-p) ;; Remove current developing packages (setq pkg-list (remove (intern (eask-guess-package-name)) pkg-list))) (eask-list pkg-list package-alist 0) (eask-msg "") (eask-info "(Total of %s dependenc%s %s outdated)" (length pkg-list) (eask--sinr pkg-list "y" "ies") (eask--sinr pkg-list "is" "are"))) (eask-msg "") (eask-info "(No outdated dependencies)"))) ;;; core/outdated.el ends here ================================================ FILE: lisp/core/package-directory.el ================================================ ;;; core/package-directory.el --- Print path to package directory -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Print path to package directory, ;; ;; $ eask package-directory ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (eask-print "%s" package-user-dir)) ;;; core/package-directory.el ends here ================================================ FILE: lisp/core/package.el ================================================ ;;; core/package.el --- Build a package artifact -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Build a package artifact, and put it into the given destination ;; ;; $ eask package [destination] ;; ;; ;; Positional options: ;; ;; [destination] destination path/folder ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function package-directory-recipe "ext:package-recipe.el") ;; ;;; Core (defun eask-package-dir--patterns () "Return patterns for directory recipe." (if eask-files (if (member eask-package-file (eask-expand-file-specs (eask-files-spec))) ;; Else we return default eask-files ;; If files DSL doesn't contain package main file, we added manually! ;; ;; This would avoid error, single file doesn't match package name. (append eask-files (list eask-package-file))) package-build-default-files-spec)) (defun eask-package-dir-recipe (version) "Form a directory recipe. Argument VERSION is a string represent the version number of this package." (eask-load "extern/package-recipe") (let* ((name (eask-guess-package-name)) (patterns (eask-package-dir--patterns)) (path default-directory) (rcp (package-directory-recipe name :name name :files patterns :dir path))) (setf (slot-value rcp 'version) version) (setf (slot-value rcp 'time) (eask-current-time)) rcp)) (defun eask-package-packaged-name () "Find a possible packaged name." (let ((name (eask-guess-package-name)) (version (eask-package-version))) (concat name "-" version))) (defun eask-package--packaged-file (ext) "Find a possible packaged file with extension (EXT)." (expand-file-name (concat (eask-package-packaged-name) "." ext) eask-dist-path)) (defun eask-package-packaged-file () "Return generated package artifact; it could be a tar or el." (if (eask-package-multi-p) (eask-package--packaged-file "tar") (eask-package--packaged-file "el"))) (eask-start (let* ((eask-dist-path (or (eask-args 0) eask-dist-path)) (eask-dist-path (expand-file-name eask-dist-path)) (packaged)) (ignore-errors (make-directory eask-dist-path t)) (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (eask-archive-install-packages '("gnu" "melpa") 'package-build) (eask-load "extern/package-build") ; override (let* ((version (eask-package-version)) (rcp (eask-package-dir-recipe version)) (package-build-working-dir default-directory) (package-build-archive-dir eask-dist-path)) (eask-msg "") (eask-with-progress (format "Building artifact %s (%s)... " (eask-package-name) version) (package-build--package rcp) "done ✓")) (setq packaged (eask-package-packaged-file) packaged (when (file-exists-p packaged) packaged)) (when (and eask-is-windows (eask-package-single-p)) (with-current-buffer (find-file packaged) (set-buffer-file-coding-system 'utf-8-unix) (save-buffer))) (eask-msg "") (eask-info "(Built in %s)" packaged))) ;;; core/package.el ends here ================================================ FILE: lisp/core/recipe.el ================================================ ;;; core/recipe.el --- Suggest a recipe format -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command would suggest you the recipe for current package: ;; ;; $ eask recipe ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-recipe-string () "Return the recipe format in string." (when-let* ((url (eask-package-desc-url))) (let* ((fetcher (cond ((string-match-p "github.com" url) 'github) ((string-match-p "gitlab.com" url) 'gitlab) (t 'git))) (url-regex (if (eq fetcher 'github) "http[s]://github.com/" "http[s]://gitlab.com/")) (repo (replace-regexp-in-string url-regex "" url)) (name (eask-guess-package-name)) (recipe `(,(intern name) :fetcher ,fetcher))) (cond ((memq fetcher '(git hg)) (nconc recipe `(:url ,url))) ((memq fetcher '(gitlab github)) (nconc recipe `(:repo ,repo)))) (when eask-files (nconc recipe `(:files ,(append '(:defaults) eask-files)))) recipe))) (eask-start (if-let* ((recipe (eask-recipe-string)) (name (eask-guess-package-name))) (progn (eask-msg "") (eask-msg "recipes/%s:" name) (eask-msg "") (eask-msg "%s" (pp-to-string recipe))) (eask-msg "") (eask-info "(Repository URL is required to form a recipe)") (eask-help "core/recipe"))) ;;; core/recipe.el ends here ================================================ FILE: lisp/core/recompile.el ================================================ ;;; core/recompile.el --- Byte-recompile `.el' files -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Byte-recompile `.el' files, ;; ;; $ eask recompile [names..] ;; ;; ;; Positionals: ;; ;; [names..] specify files to byte-recompile ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Core (eask-start (eask-call "clean/elc") (eask-write "") (eask-call "core/compile")) ;;; core/recompile.el ends here ================================================ FILE: lisp/core/refresh.el ================================================ ;;; core/refresh.el --- Download package archives -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to download package archives ;; ;; $ eask refresh ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (advice-add 'package--download-one-archive :around #'eask--package-download-one-archive) (eask-pkg-init) (eask-msg "") (eask-info "(Done refresh package archives)")) ;;; core/refresh.el ends here ================================================ FILE: lisp/core/reinstall.el ================================================ ;;; core/reinstall.el --- Reinstall packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to reinstall Emacs packages, ;; ;; $ eask reinstall [names..] ;; ;; ;; Positionals: ;; ;; [names..] name of the package to reinstall ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/package") ; load dist path (defun eask-reinstall--packages (names) "Install packages by its NAMES." (let* ((names (mapcar #'eask-intern names)) (len (length names)) (s (eask--sinr len "" "s")) (installed (cl-remove-if-not #'package-installed-p names)) (installed (length installed)) (skipped (- len installed))) (eask-log "Reinstalling %s specified package%s..." len s) (eask-msg "") (eask--package-mapc #'eask-package-reinstall names) (eask-msg "") (eask-info "(Total of %s package%s reinstalled, %s skipped)" installed s skipped))) (eask-start (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (if-let* ((names (eask-args))) ;; If package [name..] are specified, we try to install it (eask-reinstall--packages names) (if-let* ((name (intern (eask-guess-package-name))) ((package-installed-p name))) (progn (eask-call "core/uninstall") (eask-msg "") (eask-call "core/install") (eask-msg "") (eask-info "(Package `%s' reinstalled.)" name)) (eask-msg "") (eask-info "(No packages have been reintalled)") (eask-help "core/reinstall")))) ;;; core/reinstall.el ends here ================================================ FILE: lisp/core/repl.el ================================================ ;;; core/repl.el --- Start the Elisp REPL -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to start the Elisp REPL, ;; ;; $ eask repl ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-repl--old-pos nil "Record the last position to output on the screen for the `ielm' buffer.") (defun eask-repl--output () "Print the REPL result to screen." (unless eask-repl--old-pos (setq eask-repl--old-pos (point-min))) (with-current-buffer "*ielm*" (goto-char eask-repl--old-pos) (while (not (eobp)) (let ((line (thing-at-point 'line t))) (unless (string-prefix-p "ELISP> " line) (eask-print line))) (forward-line 1) (end-of-line)) ;; Record last output position (setq eask-repl--old-pos (point)))) (eask-start (require 'ielm) (ielm) (eask-repl--output) (let ((input)) (while (setq input (read-from-minibuffer (ansi-blue "ELISP> "))) (with-current-buffer "*ielm*" (insert input) (setq eask-repl--old-pos (1+ (point))) ; skip all input, move to next line (eask--silent (ielm-send-input)) (eask-repl--output))))) ;;; core/repl.el ends here ================================================ FILE: lisp/core/search.el ================================================ ;;; core/search.el --- Search packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to search Emacs packges, ;; ;; $ eask search [queries..] ;; ;; ;; Positionals: ;; ;; [queries..] query to search packages ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/list") (defun eask-search--packages (query) "Filter available packages with QUERY." (let ((result)) (dolist (package (mapcar #'car package-archive-contents)) (when (string-match-p query (eask-2str package)) (push package result))) result)) (eask-start (eask-pkg-init) (if-let* ((queries (eask-args))) (let ((result)) (dolist (query queries) (setq result (append result (eask-search--packages query)))) (delete-dups result) (eask-list result package-archive-contents) (eask-msg "") (eask-info "(Search result of %s package%s)" (length result) (eask--sinr result "" "s"))) (eask-msg "") (eask-info "(No search operation; missing queries specification)") (eask-help "core/search"))) ;;; core/search.el ends here ================================================ FILE: lisp/core/status.el ================================================ ;;; core/status.el --- Display the state of the workspace -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Display the state of the workspace ;; ;; $ eask status ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function ansi-bright-black "ext:ansi.el") (declare-function ansi-underscore "ext:ansi.el") ;; ;;; Core (defvar eask-status--info-count 0 "Count of the stratus info.") (defun eask-status--environment-name () "Get the working environment name." (cond ((eask-global-p) "global (~/)") ((eask-config-p) (format "configuration (%s)" user-emacs-directory)) (t "development (./)"))) (defun eask-status--print-title (title) "Print section TITLE." (eask-println "") (eask-println (ansi-underscore title)) (eask-println "")) (defun eask-status--print-info (fmt pair) "Print environment info with FMT and PAIR." (let ((title (eask-2str (nth 0 pair))) (content (eask-2str (nth 1 pair))) (note (eask-2str (or (nth 2 pair) "")))) (eask-println fmt title (ansi-bright-black content) note))) (defun eask-status--list-max-length (lst index) "Return the LST max length by its INDEX." (let ((max-len 0) (max-current)) (dolist (data lst) (setq max-current (eask-2str (nth index data)) max-current (pcase index (1 (ansi-bright-black max-current)) (_ max-current)) max-len (max (length max-current) max-len))) max-len)) (defun eask-status--print-infos (lst) "Print environment info LST." (let* ((len-0 (eask-2str (eask-status--list-max-length lst 0))) ; unused (len-1 (eask-2str (+ (eask-status--list-max-length lst 1) 2))) (fmt (concat " %-24s %-" len-1 "s %s"))) (dolist (pair lst) (when pair (eask-status--print-info fmt pair) (cl-incf eask-status--info-count))))) (defun eask-status--file-dir (path) "Return file directory status from PATH." (unless (file-exists-p path) (ansi-red "(missing)"))) (eask-start (eask-println "In the %s environment" (eask-status--environment-name)) (eask-println "Your emacs home is point to %s" (expand-file-name user-emacs-directory)) (eask-status--print-title "System:") (eask-status--print-infos `(("Emacs version" ,emacs-version) ("Invocation" ,invocation-directory) ("Build No." ,emacs-build-number) ("System configuration" ,system-configuration) ,(when-let* ((emacs-build-time) (time (format-time-string "%Y-%m-%d" emacs-build-time))) `("Build time" ,time)) ("System type" ,system-type))) (eask-status--print-title "Environment:") (eask-status--print-infos `(("Emacs directory" ,(expand-file-name user-emacs-directory) ,(eask-status--file-dir user-emacs-directory)) ("ELPA directory" ,(expand-file-name package-user-dir) ,(eask-status--file-dir package-user-dir)) ("ELPA directory (system)" ,eask-package-sys-dir ,(eask-status--file-dir eask-package-sys-dir)) ("early-init.el" ,(expand-file-name early-init-file) ,(eask-status--file-dir early-init-file)) (".emacs" ,(expand-file-name eask-dot-emacs-file) ,(eask-status--file-dir eask-dot-emacs-file)) ("init.el" ,(expand-file-name user-init-file) ,(eask-status--file-dir user-init-file)) ("custom.el" ,(if custom-file (expand-file-name custom-file) "nil") ,(when custom-file (eask-status--file-dir custom-file))))) (eask-status--print-title "Eask:") (eask-status--print-infos `(("Eask file" ,(or eask-file "missing")) ("Eask file Count" ,(length (eask--find-files default-directory))))) (eask-info "(Total of %s states listed)" eask-status--info-count)) ;;; core/status.el ends here ================================================ FILE: lisp/core/uninstall.el ================================================ ;;; core/uninstall.el --- Uninstall packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to uninstall Emacs packages, ;; ;; $ eask uninstall [names..] ;; ;; ;; Positionals: ;; ;; [names..] name of the package to uninstall; else we uninstall pacakge ;; from current workspace ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-uninstall--packages(names) "Uninstall packages by its NAMES." (let* ((names (mapcar #'eask-intern names)) (len (length names)) (s (eask--sinr len "" "s")) (pkg-installed (cl-remove-if-not #'package-installed-p names)) (deleted (length pkg-installed)) (skipped (- len deleted))) (eask-log "Uninstalling %s specified package%s..." len s) (eask-msg "") (eask--package-mapc #'eask-package-delete names) (eask-msg "") (eask-info "(Total of %s package%s deleted, %s skipped)" deleted s skipped))) (eask-start (eask-defvc< 27 (eask-pkg-init)) ; XXX: remove this after we drop 26.x (if-let* ((names (eask-args))) (eask-uninstall--packages names) (if-let* ((name (intern (eask-guess-package-name))) ((package-installed-p name))) (progn (eask-package-delete name) (eask-msg "") (eask-info "(Package `%s' deleted.)" name)) (eask-info "(No packages have been uninstalled)") (eask-help "core/uninstall")))) ;;; core/uninstall.el ends here ================================================ FILE: lisp/core/upgrade.el ================================================ ;;; core/upgrade.el --- Upgrade packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to upgrade Emacs packages, ;; ;; $ eask upgrade [names..] ;; ;; ;; Positionals: ;; ;; [names..] package to upgrade; else we upgrade all packages ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-upgrade--package-version-string (pkg-desc) "Get package version string with color from PKG-DESC." (let ((version (package-desc-version pkg-desc))) (ansi-yellow (package-version-join version)))) (defun eask-upgrade--package (pkg-desc) "Upgrade package using PKG-DESC." (let* ((name (package-desc-name pkg-desc)) (pkg-string (ansi-green (eask-2str name))) (version-new (eask-upgrade--package-version-string pkg-desc)) (old-pkg-desc (eask-package-desc name t)) (version-old (eask-upgrade--package-version-string old-pkg-desc))) (eask-with-progress (format " - Upgrading %s (%s) -> (%s)..." pkg-string version-old version-new) (eask-with-verbosity 'debug (when (eask-force-p) (package-delete old-pkg-desc)) (package-install pkg-desc) (unless (eask-force-p) (package-delete old-pkg-desc))) "done ✓"))) (defun eask-package--upgradable-p (pkg) "Return non-nil if PKG can be upgraded." (let ((current (eask-package--version pkg t)) (latest (eask-package--version pkg nil))) (version-list-< current latest))) (defun eask-package--upgrades () "Return a list of upgradable package description." (let (upgrades) (eask-with-progress (ansi-green "Collecting information for upgradable packages...") (dolist (pkg (mapcar #'car package-alist)) (when (eask-package--upgradable-p pkg) (push (cadr (assq pkg package-archive-contents)) upgrades))) (ansi-green "done ✓")) upgrades)) (defun eask-upgrade--package-all () "Upgrade for archive packages." (if-let* ((upgrades (eask-package--upgrades))) (progn (mapc #'eask-upgrade--package upgrades) (eask-msg "") (eask-info "(Done upgrading all packages)")) (eask-msg "") (eask-info "(All packages are up to date)"))) (eask-start (eask-pkg-init) (if-let* ((names (eask-args))) (dolist (name names) (setq name (intern name)) (if (package-installed-p name) (if (or (eask-package--upgradable-p name) (eask-force-p)) (eask-upgrade--package (cadr (assq name package-archive-contents))) (eask-warn "Package `%s` is already up to date" name)) (eask-error "Package does not exists `%s`, you need to install before upgrade" name))) (eask-upgrade--package-all))) ;;; core/upgrade.el ends here ================================================ FILE: lisp/create/el-project.el ================================================ ;;; create/el-project.el --- Create a new elisp project -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Create a new elisp project, ;; ;; $ eask create el-project ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa" "jcs-elpa") 'el-project) ;; Start project creation. (require 'el-project) (el-project-make-project)) ;;; create/el-project.el ends here ================================================ FILE: lisp/create/elpa.el ================================================ ;;; create/elpa.el --- Create a new ELPA -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Create a new ELPA, ;; ;; $ eask create elpa [name] ;; ;; ;; Positionals: ;; ;; [name] new ELPA name ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Core (defconst eask-create-elpa--template-name "template-elpa" "Holds template project name.") (eask-start (ignore-errors (delete-directory ".git" t)) (eask-with-progress "Preparing your new ELPA project... " (with-current-buffer (find-file (expand-file-name "Eask")) (goto-char (point-min)) (search-forward "(script ") (forward-line 1) (dolist (gitkeeps (directory-files-recursively eask-file-root ".gitkeep")) (ignore-errors (delete-file gitkeeps))) ;; --- Start insertion (insert "(script \"build\" \"eask exec github-elpa build\")\n") (insert "(script \"commit\" \"eask exec github-elpa commit\")\n") (insert "(script \"update\" \"eask exec github-elpa update -a \\\"./docs/packages\\\"\")\n") (search-forward "(source ") (forward-line 1) (insert "(source \"melpa\")\n") (search-forward "(depends-on ") (forward-line 1) (insert "(depends-on \"github-elpa\")\n") ;; --- End insertion (save-buffer)) "done ✓") (eask-msg "") (eask-msg "Congratulations! Your new ELPA project is created in %s" eask-file-root) (eask-msg "") (eask-msg " [1] Navigate to %s" eask-file-root) (eask-msg " [2] Try out the command `eask info`") (eask-msg " [3] See the README.md file to learn to use this project") (eask-msg "") (eask-msg "Visit https://emacs-eask.github.io/ for quickstart guide and full documentation.")) ;;; create/elpa.el ends here ================================================ FILE: lisp/create/package.el ================================================ ;;; create/package.el --- Create a new elisp project -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Create a new elisp project, ;; ;; $ eask create package [name] ;; ;; ;; Positionals: ;; ;; [name] new project name ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defconst eask-create-package--template-name "template-elisp" "Holds template project name.") (defun eask-create-package--replace-s-in-buffer (old new) "Replace OLD to NEW in buffer." (let ((str (buffer-string))) (setq str (eask-s-replace old new str)) (delete-region (point-min) (point-max)) (insert str))) ;; XXX: we can't use `user-full-name' in batch-mode. It will always return ;; empty string. (defun eask-create-package--get-user () "Return user name." (string-trim (shell-command-to-string "git config user.name"))) ;; XXX: we can't use `user-mail-address' in batch-mode. It will always return ;; empty string. (defun eask-create-package--get-mail () "Return user email." (string-trim (shell-command-to-string "git config user.email"))) (eask-start (ignore-errors (delete-directory ".git" t)) (eask-with-progress "Preparing your new elisp project... " (let ((template-package-file (expand-file-name (concat eask-create-package--template-name ".el")))) (rename-file template-package-file eask-package-file) (with-current-buffer (find-file eask-package-file) (eask-create-package--replace-s-in-buffer eask-create-package--template-name (eask-package-name)) (eask-create-package--replace-s-in-buffer "{ SUMMARY }" (eask-package-description)) (eask-create-package--replace-s-in-buffer "{ YEAR }" (format-time-string "%Y")) (eask-create-package--replace-s-in-buffer "{ FULL_NAME }" (eask-create-package--get-user)) (eask-create-package--replace-s-in-buffer "{ MAIL_ADDR }" (eask-create-package--get-mail)) (eask-create-package--replace-s-in-buffer "{ WEBSITE_URL }" (or eask-website-url "")) (eask-create-package--replace-s-in-buffer "{ VERSION }" (eask-package-version)) (eask-create-package--replace-s-in-buffer "{ EMACS_VERSION }" (eask-depends-emacs-version)) (eask-create-package--replace-s-in-buffer "{ KEYWORDS }" (string-join eask-keywords " ")) (save-buffer))) "done ✓") (eask-msg "") (eask-msg "Congratulations! Your new Elisp project is created in %s" eask-file-root) (eask-msg "") (eask-msg " [1] Navigate to %s" eask-file-root) (eask-msg " [2] Try out the command `eask info`") (eask-msg "") (eask-msg "Visit https://emacs-eask.github.io/ for quickstart guide and full documentation.")) ;;; create/package.el ends here ================================================ FILE: lisp/docs/el2org.el ================================================ ;;; docs/el2org.el --- Build documentation with el2org -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Build documentation for your Elisp project, ;; ;; $ eask docs [names..] ;; ;; ;; Positionals: ;; ;; [names..] specify files to scan ;; ;; Action options: ;; ;; [destination] optional output destination ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Flags (eask-command-check "28.1") ;; ;;; Externals (require 'el2org nil t) ;; ;;; Core (defun eask-docs--to-html (el-file) "Generate html file from EL-FILE." (interactive) (let* ((filename (file-name-nondirectory el-file)) (html-file (expand-file-name (concat (file-name-sans-extension filename) ".html") eask-docs-path))) (eask-with-verbosity 'debug (el2org-generate-file el-file nil 'html html-file t)))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'el2org) ;; Start building... (require 'el2org) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files))) (eask-docs-path (or (eask-dest) eask-docs-path))) (cond ;; Files found, do the action! (files (eask-debug "Destination path in %s" eask-docs-path) (ignore-errors (make-directory eask-docs-path t)) (eask-progress-seq " - Processing" files "done! ✓" #'eask-docs--to-html) (eask-msg "") (eask-info "Done. (Converted %s file%s)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No elisp source can be read)") (eask-help "core/docs"))))) ;;; docs/el2org.el ends here ================================================ FILE: lisp/extern/ansi.el ================================================ ;;; ansi.el --- Turn string into ansi strings -*- lexical-binding: t; -*- ;; Copyright (C) 2010-2013 Johan Andersson ;; Author: Johan Andersson ;; Maintainer: Johan Andersson ;; Version: 0.4.1 ;; Keywords: terminals color ansi ;; URL: http://github.com/rejeep/ansi ;; Package-Requires: ((emacs "24.1") (cl-lib "0.6")) ;; This file is NOT part of GNU Emacs. ;;; License: ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Turns simple strings to ansi strings. ;; Turning a string into an ansi string can be to add color to a ;; text, add color in the background of a text or adding a style, ;; such as bold, underscore or italic. ;;; Code: (require 'cl-lib) (defgroup ansi nil "Turn string into ansi strings." :group 'lisp) (defcustom ansi-inhibit-ansi nil "If non-nil, no apply ANSI code. This variable affects `with-ansi', `with-ansi-princ'." :group 'ansi :type 'boolean) (defconst ansi-colors '((black . 30) (red . 31) (green . 32) (yellow . 33) (blue . 34) (magenta . 35) (cyan . 36) (white . 37)) "List of text colors.") (defconst ansi-bright-colors '((bright-black . 90) (bright-red . 91) (bright-green . 92) (bright-yellow . 93) (bright-blue . 94) (bright-magenta . 95) (bright-cyan . 96) (bright-white . 97)) "List of text colors.") (defconst ansi-on-colors '((on-black . 40) (on-red . 41) (on-green . 42) (on-yellow . 43) (on-blue . 44) (on-magenta . 45) (on-cyan . 46) (on-white . 47)) "List of colors to draw text on.") (defconst ansi-on-bright-colors '((on-bright-black . 100) (on-bright-red . 101) (on-bright-green . 102) (on-bright-yellow . 103) (on-bright-blue . 104) (on-bright-magenta . 105) (on-bright-cyan . 106) (on-bright-white . 107)) "List of colors to draw text on.") (defconst ansi-styles '((bold . 1) (dark . 2) (italic . 3) (underscore . 4) (blink . 5) (rapid . 6) (contrary . 7) (concealed . 8) (strike . 9)) "List of styles.") (defvar ansi-csis '((up . "A") (down . "B") (forward . "C") (backward . "D") (next-line . "E") (previous-line . "F") (column . "G") (kill . "K")) "CSI (Control Sequence Introducer) sequences") (defconst ansi-reset 0 "Ansi code for reset.") (defun ansi--concat (&rest sequences) "Concat string elements in SEQUENCES." (apply #'concat (cl-remove-if-not 'stringp sequences))) (defun ansi--code (effect) "Return code for EFFECT." (or (cdr (assoc effect ansi-colors)) (cdr (assoc effect ansi-bright-colors)) (cdr (assoc effect ansi-on-colors)) (cdr (assoc effect ansi-on-bright-colors)) (cdr (assoc effect ansi-styles)))) (defun ansi--is-alias (effect) "Return non-nil if EFFECT is available in DSL." (or (car (assoc effect ansi-colors)) (car (assoc effect ansi-bright-colors)) (car (assoc effect ansi-on-colors)) (car (assoc effect ansi-on-bright-colors)) (car (assoc effect ansi-styles)) (car (assoc effect ansi-csis)))) (defun ansi--char (effect) "Return char for EFFECT." (cdr (assoc effect ansi-csis))) (defmacro ansi--define (effect) "Define ansi function with EFFECT." (let ((fn-name (intern (format "ansi-%s" (symbol-name effect))))) `(defun ,fn-name (format-string &rest objects) ,(format "Add '%s' ansi effect to text." effect) (apply 'ansi-apply ',effect format-string objects)))) (cl-eval-when (compile eval load) (defun ansi--substitute (body) (if (listp body) (if (ansi--is-alias (car body)) `(,(intern (format "ansi-%s" (symbol-name (car body)))) ,@(mapcar (lambda (x) (ansi--substitute x)) (cdr body))) (mapcar (lambda (x) (ansi--substitute x)) body)) body))) (defmacro with-ansi (&rest body) "Shortcut names (without ansi- prefix) can be used in this BODY." `(ansi--concat ,@(ansi--substitute (mapcar #'macroexpand-all body)))) (defmacro with-ansi-princ (&rest body) "Shortcut names (without ansi- prefix) can be used in this BODY and princ." `(princ (with-ansi ,@body))) (defun ansi-apply (effect-or-code format-string &rest objects) "Apply EFFECT-OR-CODE to text. FORMAT-STRING and OBJECTS are processed same as `apply'." (let ((code (if (numberp effect-or-code) effect-or-code (ansi--code effect-or-code))) (text (apply 'format format-string objects))) (if ansi-inhibit-ansi text (format "\e[%dm%s\e[%sm" code text ansi-reset)))) (defun ansi-csi-apply (effect-or-char &optional reps) "Apply EFFECT-OR-CHAR REPS (1 default) number of times." (if ansi-inhibit-ansi "" (let ((char (if (symbolp effect-or-char) (ansi--char effect-or-char) effect-or-char))) (format "\u001b[%d%s" (or reps 1) char)))) (defun ansi-up (&optional n) "Move N steps (1 step default) up." (ansi-csi-apply 'up n)) (defun ansi-down (&optional n) "Move N steps (1 step default) down." (ansi-csi-apply 'down n)) (defun ansi-forward (&optional n) "Move N steps (1 step default) forward." (ansi-csi-apply 'forward n)) (defun ansi-backward (&optional n) "Move N steps (1 step default) backward." (ansi-csi-apply 'backward n)) (defun ansi-next-line (&optional n) "Moves cursor to beginning of the line N (default 1) lines down." (ansi-csi-apply 'next-line n)) (defun ansi-previous-line (&optional n) "Moves cursor to beginning of the line N (default 1) lines up." (ansi-csi-apply 'previous-line n)) (defun ansi-column (&optional n) "Moves the cursor to column N (default 1)" (ansi-csi-apply 'column n)) (defun ansi-kill (&optional n) "Erase part of the line. If N is 0 (or missing), clear from cursor to the end of the line. If N is 1, clear from cursor to beginning of the line. If N is 2, clear entire line. Cursor position does not change." (ansi-csi-apply 'kill n)) (ansi--define black) (ansi--define red) (ansi--define green) (ansi--define yellow) (ansi--define blue) (ansi--define magenta) (ansi--define cyan) (ansi--define white) (ansi--define bright-black) (ansi--define bright-red) (ansi--define bright-green) (ansi--define bright-yellow) (ansi--define bright-blue) (ansi--define bright-magenta) (ansi--define bright-cyan) (ansi--define bright-white) (ansi--define on-black) (ansi--define on-red) (ansi--define on-green) (ansi--define on-yellow) (ansi--define on-blue) (ansi--define on-magenta) (ansi--define on-cyan) (ansi--define on-white) (ansi--define on-bright-black) (ansi--define on-bright-red) (ansi--define on-bright-green) (ansi--define on-bright-yellow) (ansi--define on-bright-blue) (ansi--define on-bright-magenta) (ansi--define on-bright-cyan) (ansi--define on-bright-white) (ansi--define bold) (ansi--define dark) (ansi--define italic) (ansi--define underscore) (ansi--define blink) (ansi--define rapid) (ansi--define contrary) (ansi--define concealed) (ansi--define strike) (provide 'ansi) ;;; ansi.el ends here ================================================ FILE: lisp/extern/github-elpa.el ================================================ ;;; extern/github-elpa.el --- External module `github-elpa' -*- lexical-binding: t; -*- ;;; Commentary: ;;; Code: (unless eask-depends-on-recipe-p (defvar github-elpa-working-dir (expand-file-name "./temp-elpa/.working/" user-emacs-directory)) (defvar github-elpa-archive-dir (expand-file-name "./temp-elpa/packages/" user-emacs-directory)) (defvar github-elpa-recipes-dir (expand-file-name "./temp-elpa/recipes/" user-emacs-directory)) (ignore-errors (make-directory github-elpa-working-dir t)) (ignore-errors (make-directory github-elpa-archive-dir t)) (ignore-errors (delete-directory github-elpa-recipes-dir t)) (ignore-errors (make-directory github-elpa-recipes-dir t))) (defun github-elpa-build () "Github elpa build." (eask-load "extern/package-build") ; override (let ((package-build-working-dir github-elpa-working-dir) (package-build-archive-dir github-elpa-archive-dir) (package-build-recipes-dir github-elpa-recipes-dir)) ;;(github-elpa--git-check-repo) ;;(github-elpa--git-check-workdir-clean) (make-directory package-build-archive-dir t) ;; Currently no way to detect build failure... (dolist (recipe (directory-files package-build-recipes-dir nil "^[^.]")) (message "") (message "") (message ":: temp-elpa: packaging recipe %s" recipe) (package-build-archive recipe)) (package-build-cleanup))) ;;; extern/github-elpa.el ends here ================================================ FILE: lisp/extern/package-build/24/package-build-badges.el ================================================ ;;; package-build-badges.el --- Create batches for packages ;; Copyright (C) 2011-2013 Donald Ephraim Curtis ;; Copyright (C) 2012-2014 Steve Purcell ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Keywords: tools ;; This file is not (yet) part of GNU Emacs. ;; However, it is distributed under the same license. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; In future we should provide a hook. Note also that it would be ;; straightforward to generate the SVG ourselves, which would save ;; the network overhead. ;;; Code: (require 'package-build) (defun package-build--write-melpa-badge-image (name version target-dir) (shell-command (mapconcat #'shell-quote-argument (list "curl" "-f" "-o" (expand-file-name (concat name "-badge.svg") target-dir) (format "https://img.shields.io/badge/%s-%s-%s.svg" (if package-build-stable "melpa stable" "melpa") (url-hexify-string version) (if package-build-stable "3e999f" "922793"))) " "))) (provide 'package-build-badges) ;; Local Variables: ;; coding: utf-8 ;; checkdoc-minor-mode: 1 ;; indent-tabs-mode: nil ;; End: ;;; package-badges.el ends here ================================================ FILE: lisp/extern/package-build/24/package-build.el ================================================ ;;; package-build.el --- Tools for assembling a package archive ;; Copyright (C) 2011-2020 Donald Ephraim Curtis ;; Copyright (C) 2012-2020 Steve Purcell ;; Copyright (C) 2016-2020 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Keywords: tools ;; Homepage: https://github.com/melpa/package-build ;; Package-Requires: ((cl-lib "0.5") (emacs "24.1")) ;; Package-Version: 0-git ;; This file is not (yet) part of GNU Emacs. ;; However, it is distributed under the same license. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This file allows a curator to publish an archive of Emacs packages. ;; The archive is generated from a set of recipes which describe elisp ;; projects and repositories from which to get them. The term ;; "package" here is used to mean a specific version of a project that ;; is prepared for download and installation. ;;; Code: (require 'cl-lib) (require 'package) (require 'lisp-mnt) (require 'json) (require 'package-recipe) ;;; Options (defconst package-build--melpa-base (file-name-directory (directory-file-name (file-name-directory (or load-file-name (buffer-file-name)))))) (defgroup package-build nil "Facilities for building package.el-compliant packages from upstream source code." :group 'development) (defcustom package-build-working-dir (expand-file-name "working/" package-build--melpa-base) "Directory in which to keep checkouts." :group 'package-build :type 'string) (defcustom package-build-archive-dir (expand-file-name "packages/" package-build--melpa-base) "Directory in which to keep compiled archives." :group 'package-build :type 'string) (defcustom package-build-recipes-dir (expand-file-name "recipes/" package-build--melpa-base) "Directory containing recipe files." :group 'package-build :type 'string) (defcustom package-build-verbose t "When non-nil, then print additional progress information." :group 'package-build :type 'boolean) (defcustom package-build-stable nil "When non-nil, then try to build packages from versions-tagged code." :group 'package-build :type 'boolean) (defcustom package-build-timeout-executable "timeout" "Path to a GNU coreutils \"timeout\" command if available. This must be a version which supports the \"-k\" option. On MacOS it is possible to install coreutils using Homebrew or similar, which will provide the GNU timeout program as \"gtimeout\"." :group 'package-build :type '(file :must-match t)) (defcustom package-build-timeout-secs nil "Wait this many seconds for external processes to complete. If an external process takes longer than specified here to complete, then it is terminated. If nil, then no time limit is applied. This setting requires `package-build-timeout-executable' to be set." :group 'package-build :type 'number) (defcustom package-build-tar-executable "tar" "Path to a (preferably GNU) tar command. Certain package names (e.g. \"@\") may not work properly with a BSD tar. On MacOS it is possible to install coreutils using Homebrew or similar, which will provide the GNU timeout program as \"gtar\"." :group 'package-build :type '(file :must-match t)) (defcustom package-build-write-melpa-badge-images nil "When non-nil, write MELPA badge images alongside packages. These batches can, for example, be used on GitHub pages." :group 'package-build :type 'boolean) (defcustom package-build-version-regexp "^[rRvV]?\\(.*\\)$" "Default pattern for matching valid version-strings within repository tags. The string in the capture group should be parsed as valid by `version-to-list'." :group 'package-build :type 'string) ;;; Generic Utilities (defun package-build--message (format-string &rest args) "Behave like `message' if `package-build-verbose' is non-nil. Otherwise do nothing. FORMAT-STRING and ARGS are as per that function." (when package-build-verbose (apply 'message format-string args))) ;;; Version Handling (defun package-build--parse-time (str &optional regexp) "Parse STR as a time, and format as a YYYYMMDD.HHMM string. Always use Coordinated Universal Time (UTC) for output string. If REGEXP is provided, it is applied to STR and the function parses the first match group instead of STR." (unless str (error "No valid timestamp found")) (setq str (substring-no-properties str)) (when regexp (if (string-match regexp str) (setq str (match-string 1 str)) (error "No valid timestamp found"))) ;; We remove zero-padding the HH portion, as it is lost ;; when stored in the archive-contents (let ((time (date-to-time (if (string-match "\ ^\\([0-9]\\{4\\}\\)/\\([0-9]\\{2\\}\\)/\\([0-9]\\{2\\}\\) \ \\([0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)$" str) (concat (match-string 1 str) "-" (match-string 2 str) "-" (match-string 3 str) " " (match-string 4 str)) str)))) (concat (format-time-string "%Y%m%d." time t) (format "%d" (string-to-number (format-time-string "%H%M" time t)))))) (defun package-build--find-version-newest (tags &optional regexp) "Find the newest version in TAGS matching REGEXP. If optional REGEXP is nil, then `package-build-version-regexp' is used instead." (let ((ret '(nil 0))) (dolist (tag tags) (string-match (or regexp package-build-version-regexp) tag) (let ((version (ignore-errors (version-to-list (match-string 1 tag))))) (when (and version (version-list-<= (cdr ret) version)) (setq ret (cons tag version)))) ;; Some version tags use "_" as version separator instead of ;; the default ".", e.g. "1_4_5". Check for valid versions ;; again, this time using "_" as a `version-separator'. ;; Since "_" is otherwise treated as a snapshot separator by ;; `version-regexp-alist', we don't have to worry about the ;; incorrect version list above `(1 -4 4 -4 5)' since it will ;; always be treated as smaller by `version-list-<'. (string-match (or regexp package-build-version-regexp) tag) (let* ((version-separator "_") (version (ignore-errors (version-to-list (match-string 1 tag))))) (when (and version (version-list-<= (cdr ret) version)) (setq ret (cons tag version))))) (and (car ret) (cons (car ret) (package-version-join (cdr ret)))))) ;;; Run Process (defun package-build--run-process (directory destination command &rest args) (with-current-buffer (if (eq destination t) (current-buffer) (or destination (get-buffer-create "*package-build-checkout*"))) (let ((default-directory (file-name-as-directory (or directory default-directory))) (argv (nconc (unless (eq system-type 'windows-nt) (list "env" "LC_ALL=C")) (if (and package-build-timeout-secs package-build-timeout-executable) (nconc (list package-build-timeout-executable "-k" "60" (number-to-string package-build-timeout-secs) command) args) (cons command args))))) (unless (file-directory-p default-directory) (error "Can't run process in non-existent directory: %s" default-directory)) (let ((exit-code (apply 'process-file (car argv) nil (current-buffer) t (cdr argv)))) (or (zerop exit-code) (error "Command '%s' exited with non-zero status %d: %s" argv exit-code (buffer-string))))))) (defun package-build--run-process-match (regexp directory command &rest args) (with-temp-buffer (apply 'package-build--run-process directory t command args) (goto-char (point-min)) (re-search-forward regexp) (match-string-no-properties 1))) (defun package-build--process-lines (directory command &rest args) (with-temp-buffer (apply 'package-build--run-process directory t command args) (split-string (buffer-string) "\n" t))) ;;; Checkout ;;;; Common (defmethod package-build--checkout :before ((rcp package-recipe)) (package-build--message "Package: %s" (oref rcp name)) (package-build--message "Fetcher: %s" (substring (symbol-name (with-no-warnings ;; Use eieio-object-class once we ;; no longer support Emacs 24.3. (object-class-fast rcp))) 8 -7)) (package-build--message "Source: %s\n" (package-recipe--upstream-url rcp))) ;;;; Git (defmethod package-build--checkout ((rcp package-git-recipe)) (let ((dir (package-recipe--working-tree rcp)) (url (package-recipe--upstream-url rcp))) (cond ((and (file-exists-p (expand-file-name ".git" dir)) (string-equal (package-build--used-url rcp) url)) (package-build--message "Updating %s" dir) (package-build--run-process dir nil "git" "fetch" "-f" "--all" "--tags")) (t (when (file-exists-p dir) (delete-directory dir t)) (package-build--message "Cloning %s to %s" url dir) (package-build--run-process nil nil "git" "clone" url dir))) (if package-build-stable (cl-destructuring-bind (tag . version) (or (package-build--find-version-newest (let ((default-directory (package-recipe--working-tree rcp))) (process-lines "git" "tag")) (oref rcp version-regexp)) (error "No valid stable versions found for %s" (oref rcp name))) (package-build--checkout-1 rcp (concat "tags/" tag)) version) (package-build--checkout-1 rcp) (package-build--parse-time (car (apply #'package-build--process-lines dir "git" "log" "--first-parent" "-n1" "--pretty=format:'\%ci'" (package-build--expand-source-file-list rcp))) (oref rcp tag-regexp))))) (defmethod package-build--checkout-1 ((rcp package-git-recipe) &optional rev) (let ((dir (package-recipe--working-tree rcp))) (unless rev (setq rev (or (oref rcp commit) (concat "origin/" (or (oref rcp branch) (ignore-errors (package-build--run-process-match "HEAD branch: \\(.*\\)" dir "git" "remote" "show" "origin")) "master"))))) (package-build--run-process dir nil "git" "reset" "--hard" rev) (package-build--run-process dir nil "git" "submodule" "sync" "--recursive") (package-build--run-process dir nil "git" "submodule" "update" "--init" "--recursive"))) (defmethod package-build--used-url ((rcp package-git-recipe)) (let ((default-directory (package-recipe--working-tree rcp))) (car (process-lines "git" "config" "remote.origin.url")))) ;;;; Hg (defmethod package-build--checkout ((rcp package-hg-recipe)) (let ((dir (package-recipe--working-tree rcp)) (url (package-recipe--upstream-url rcp))) (cond ((and (file-exists-p (expand-file-name ".hg" dir)) (string-equal (package-build--used-url rcp) url)) (package-build--message "Updating %s" dir) (package-build--run-process dir nil "hg" "pull") (package-build--run-process dir nil "hg" "update")) (t (when (file-exists-p dir) (delete-directory dir t)) (package-build--message "Cloning %s to %s" url dir) (package-build--run-process nil nil "hg" "clone" url dir))) (if package-build-stable (cl-destructuring-bind (tag . version) (or (package-build--find-version-newest (mapcar (lambda (line) ;; Remove space and rev that follow ref. (string-match "\\`[^ ]+" line) (match-string 0)) (process-lines "hg" "tags")) (oref rcp version-regexp)) (error "No valid stable versions found for %s" (oref rcp name))) (package-build--run-process dir nil "hg" "update" tag) version) (package-build--parse-time (car (apply #'package-build--process-lines dir "hg" "log" "--style" "compact" "-l1" (package-build--expand-source-file-list rcp))) (oref rcp tag-regexp))))) (defmethod package-build--used-url ((rcp package-hg-recipe)) (package-build--run-process-match "default = \\(.*\\)" (package-recipe--working-tree rcp) "hg" "paths")) ;;; Various Files (defun package-build--write-pkg-file (pkg-file pkg-info) "Write PKG-FILE containing PKG-INFO." (with-temp-file pkg-file (pp `(define-package ,(aref pkg-info 0) ,(aref pkg-info 3) ,(aref pkg-info 2) ',(mapcar (lambda (elt) (list (car elt) (package-version-join (cadr elt)))) (aref pkg-info 1)) ;; Append our extra information ,@(cl-mapcan (lambda (entry) (let ((value (cdr entry))) (when (or (symbolp value) (listp value)) ;; We must quote lists and symbols, ;; because Emacs 24.3 and earlier evaluate ;; the package information, which would ;; break for unquoted symbols or lists (setq value (list 'quote value))) (list (car entry) value))) (when (> (length pkg-info) 4) (aref pkg-info 4)))) (current-buffer)) (princ ";; Local Variables:\n;; no-byte-compile: t\n;; End:\n" (current-buffer)))) (defun package-build--create-tar (file dir &optional files) "Create a tar FILE containing the contents of DIR, or just FILES if non-nil." (when (eq system-type 'windows-nt) (setq file (replace-regexp-in-string "^\\([a-z]\\):" "/\\1" file))) (apply 'process-file package-build-tar-executable nil (get-buffer-create "*package-build-checkout*") nil "-cvf" file "--exclude=.git" "--exclude=.hg" (or (mapcar (lambda (fn) (concat dir "/" fn)) files) (list dir)))) (defun package-build--find-package-commentary (file-path) "Get commentary section from FILE-PATH." (when (file-exists-p file-path) (with-temp-buffer (insert-file-contents file-path) (lm-commentary)))) (defun package-build--write-pkg-readme (target-dir commentary file-name) "In TARGET-DIR, write COMMENTARY to a -readme.txt file prefixed with FILE-NAME." (when commentary (with-temp-buffer (insert commentary) ;; Adapted from `describe-package-1'. (goto-char (point-min)) (save-excursion (when (re-search-forward "^;;; Commentary:\n" nil t) (replace-match "")) (while (re-search-forward "^\\(;+ ?\\)" nil t) (replace-match "")) (goto-char (point-min)) (when (re-search-forward "\\`\\( *\n\\)+" nil t) (replace-match ""))) (delete-trailing-whitespace) (let ((coding-system-for-write buffer-file-coding-system)) (write-region nil nil (expand-file-name (concat file-name "-readme.txt") target-dir)))))) ;;; Entries (defun package-build--update-or-insert-header (name value) "Ensure current buffer has NAME header with the given VALUE. Any existing header will be preserved and given the \"X-Original-\" prefix. If VALUE is nil, the new header will not be inserted, but any original will still be renamed." (goto-char (point-min)) (if (let ((case-fold-search t)) (re-search-forward (concat "^;+* *" (regexp-quote name) " *: *") nil t)) (progn (move-beginning-of-line nil) (search-forward "V" nil t) (backward-char) (insert "X-Original-") (move-beginning-of-line nil)) ;; Put the new header in a sensible place if we can (re-search-forward "^;+* *\\(Version\\|Package-Requires\\|Keywords\\|URL\\) *:" nil t) (forward-line)) (insert (format ";; %s: %s" name value)) (newline)) (defun package-build--ensure-ends-here-line (file-path) "Add a 'FILE-PATH ends here' trailing line if missing." (save-excursion (goto-char (point-min)) (let ((trailer (concat ";;; " (file-name-nondirectory file-path) " ends here"))) (unless (search-forward trailer nil t) (goto-char (point-max)) (newline) (insert trailer) (newline))))) (defun package-build--get-package-info (file-path) "Get a vector of package info from the docstrings in FILE-PATH." (when (file-exists-p file-path) (ignore-errors (with-temp-buffer (insert-file-contents file-path) ;; next few lines are a hack for some packages that aren't ;; commented properly. (package-build--update-or-insert-header "Package-Version" "0") (package-build--ensure-ends-here-line file-path) (cl-flet ((package-strip-rcs-id (str) "0")) (package-build--package-buffer-info-vec)))))) (defun package-build--package-buffer-info-vec () "Return a vector of package info. `package-buffer-info' returns a vector in older Emacs versions, and a cl struct in Emacs HEAD. This wrapper normalises the results." (let ((desc (package-buffer-info)) (keywords (lm-keywords-list))) (if (and (fboundp 'package-desc-name) (fboundp 'package-desc-extras) (fboundp 'package-desc-summary) (fboundp 'package-desc-version)) (let ((extras (package-desc-extras desc))) (when (and keywords (not (assq :keywords extras))) (push (cons :keywords keywords) extras)) (vector (package-desc-name desc) (package-desc-reqs desc) (package-desc-summary desc) (package-desc-version desc) extras)) (let ((homepage (package-build--lm-homepage)) extras) (when keywords (push (cons :keywords keywords) extras)) (when homepage (push (cons :url homepage) extras)) (vector (aref desc 0) (aref desc 1) (aref desc 2) (aref desc 3) extras))))) (defun package-build--get-pkg-file-info (file-path) "Get a vector of package info from \"-pkg.el\" file FILE-PATH." (when (file-exists-p file-path) (let ((package-def (with-temp-buffer (insert-file-contents file-path) (read (current-buffer))))) (if (eq 'define-package (car package-def)) (let* ((pkgfile-info (cdr package-def)) (descr (nth 2 pkgfile-info)) (rest-plist (cl-subseq pkgfile-info (min 4 (length pkgfile-info)))) (extras (let (alist) (while rest-plist (unless (memq (car rest-plist) '(:kind :archive)) (let ((value (cadr rest-plist))) (when value (push (cons (car rest-plist) (if (eq (car-safe value) 'quote) (cadr value) value)) alist)))) (setq rest-plist (cddr rest-plist))) alist))) (when (string-match "[\r\n]" descr) (error "Illegal multi-line package description in %s" file-path)) (vector (nth 0 pkgfile-info) (mapcar (lambda (elt) (unless (symbolp (car elt)) (error "Invalid package name in dependency: %S" (car elt))) (list (car elt) (version-to-list (cadr elt)))) (eval (nth 3 pkgfile-info))) descr (nth 1 pkgfile-info) extras)) (error "No define-package found in %s" file-path))))) (defun package-build--merge-package-info (pkg-info name version commit) "Return a version of PKG-INFO updated with NAME, VERSION and info from CONFIG. If PKG-INFO is nil, an empty one is created. If a COMMIT string is included, a corresponding :commit metadata value is included." (let ((merged (or (copy-sequence pkg-info) (vector name nil "No description available." version nil)))) (aset merged 0 name) (aset merged 3 version) (when commit (aset merged 4 (cons (cons :commit commit) (elt pkg-info 4)))) merged)) (defun package-build--write-archive-entry (rcp pkg-info type) (let ((entry (package-build--archive-entry rcp pkg-info type))) (with-temp-file (package-build--archive-entry-file entry) (print entry (current-buffer))))) (defmethod package-build--get-commit ((rcp package-git-recipe)) (ignore-errors (package-build--run-process-match "\\(.*\\)" (package-recipe--working-tree rcp) "git" "rev-parse" "HEAD"))) (defmethod package-build--get-commit ((rcp package-hg-recipe)) (ignore-errors (package-build--run-process-match "changeset:[[:space:]]+[[:digit:]]+:\\([[:xdigit:]]+\\)" (package-recipe--working-tree rcp) "hg" "log" "--debug" "--limit=1"))) (defun package-build--archive-entry (rcp pkg-info type) (let ((name (intern (aref pkg-info 0))) (requires (aref pkg-info 1)) (desc (or (aref pkg-info 2) "No description available.")) (version (aref pkg-info 3)) (extras (and (> (length pkg-info) 4) (aref pkg-info 4)))) (cons name (vector (version-to-list version) requires desc type extras)))) (defun package-build--artifact-file (archive-entry) "Return the path of the file in which the package for ARCHIVE-ENTRY is stored." (let* ((name (car archive-entry)) (pkg-info (cdr archive-entry)) (version (package-version-join (aref pkg-info 0))) (flavour (aref pkg-info 3))) (expand-file-name (format "%s-%s.%s" name version (if (eq flavour 'single) "el" "tar")) package-build-archive-dir))) (defun package-build--archive-entry-file (archive-entry) "Return the path of the file in which the package for ARCHIVE-ENTRY is stored." (let* ((name (car archive-entry)) (pkg-info (cdr archive-entry)) (version (package-version-join (aref pkg-info 0)))) (expand-file-name (format "%s-%s.entry" name version) package-build-archive-dir))) ;;; File Specs (defconst package-build-default-files-spec '("*.el" "*.el.in" "dir" "*.info" "*.texi" "*.texinfo" "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo" (:exclude ".dir-locals.el" "test.el" "tests.el" "*-test.el" "*-tests.el")) "Default value for :files attribute in recipes.") (defun package-build-expand-file-specs (dir specs &optional subdir allow-empty) "In DIR, expand SPECS, optionally under SUBDIR. The result is a list of (SOURCE . DEST), where SOURCE is a source file path and DEST is the relative path to which it should be copied. If the resulting list is empty, an error will be reported. Pass t for ALLOW-EMPTY to prevent this error." (let ((default-directory dir) (prefix (if subdir (format "%s/" subdir) "")) (lst)) (dolist (entry specs lst) (setq lst (if (consp entry) (if (eq :exclude (car entry)) (cl-nset-difference lst (package-build-expand-file-specs dir (cdr entry) nil t) :key 'car :test 'equal) (nconc lst (package-build-expand-file-specs dir (cdr entry) (concat prefix (car entry)) t))) (nconc lst (mapcar (lambda (f) (let ((destname))) (cons f (concat prefix (replace-regexp-in-string "\\.el\\.in\\'" ".el" (file-name-nondirectory f))))) (file-expand-wildcards entry)))))) (when (and (null lst) (not allow-empty)) (error "No matching file(s) found in %s: %s" dir specs)) lst)) (defun package-build--config-file-list (rcp) (let ((file-list (oref rcp files))) (cond ((null file-list) package-build-default-files-spec) ((eq :defaults (car file-list)) (append package-build-default-files-spec (cdr file-list))) (t file-list)))) (defun package-build--expand-source-file-list (rcp) (mapcar 'car (package-build-expand-file-specs (package-recipe--working-tree rcp) (package-build--config-file-list rcp)))) ;;; Info Manuals (defun package-build--generate-info-files (files source-dir target-dir) "Create .info files from any .texi files listed in FILES. The source and destination file paths are expanded in SOURCE-DIR and TARGET-DIR respectively. Any of the original .texi(nfo) files found in TARGET-DIR are deleted." (dolist (spec files) (let* ((source-file (car spec)) (source-path (expand-file-name source-file source-dir)) (dest-file (cdr spec)) (info-path (expand-file-name (concat (file-name-sans-extension dest-file) ".info") target-dir))) (when (string-match ".texi\\(nfo\\)?$" source-file) (unless (file-exists-p info-path) (ignore-errors (package-build--run-process (file-name-directory source-path) nil "makeinfo" source-path "-o" info-path) (package-build--message "Created %s" info-path))) (package-build--message "Removing %s" (expand-file-name dest-file target-dir)) (delete-file (expand-file-name dest-file target-dir)))))) (defun package-build--generate-dir-file (files target-dir) "Create dir file from any .info files listed in FILES in TARGET-DIR." (dolist (spec files) (let* ((source-file (car spec)) (dest-file (cdr spec)) (info-path (expand-file-name (concat (file-name-sans-extension dest-file) ".info") target-dir))) (when (and (or (string-match ".info$" source-file) (string-match ".texi\\(nfo\\)?$" source-file)) (file-exists-p info-path)) (ignore-errors (package-build--run-process nil nil "install-info" (concat "--dir=" (expand-file-name "dir" target-dir)) info-path)))))) ;;; Building Utilities (defun package-build--copy-package-files (files source-dir target-dir) "Copy FILES from SOURCE-DIR to TARGET-DIR. FILES is a list of (SOURCE . DEST) relative filepath pairs." (package-build--message "Copying files (->) and directories (=>)\n from %s\n to %s" source-dir target-dir) (dolist (elt files) (let* ((src (car elt)) (dst (cdr elt)) (src* (expand-file-name src source-dir)) (dst* (expand-file-name dst target-dir))) (make-directory (file-name-directory dst*) t) (cond ((file-regular-p src*) (package-build--message " %s %s -> %s" (if (equal src dst) " " "!") src dst) (copy-file src* dst*)) ((file-directory-p src*) (package-build--message " %s %s => %s" (if (equal src dst) " " "!") src dst) (copy-directory src* dst*)))))) (defconst package-build--this-file load-file-name) ;;; Building ;;;###autoload (defun package-build-archive (name &optional dump-archive-contents) "Build a package archive for the package named NAME. if DUMP-ARCHIVE-CONTENTS is non-nil, the updated archive contents are subsequently dumped." (interactive (list (package-recipe-read-name) t)) (let ((start-time (current-time)) (rcp (package-recipe-lookup name))) (unless (file-exists-p package-build-archive-dir) (package-build--message "Creating directory %s" package-build-archive-dir) (make-directory package-build-archive-dir)) (let ((default-directory package-build-working-dir) (version (package-build--checkout rcp))) (package-build--package rcp version) (when package-build-write-melpa-badge-images (package-build--write-melpa-badge-image name version package-build-archive-dir)) (package-build--message "Built %s in %.3fs, finished at %s" name (float-time (time-since start-time)) (current-time-string)) (list name version))) (when dump-archive-contents (package-build-dump-archive-contents))) ;;;###autoload (defun package-build--package (rcp version) "Create version VERSION of the package specified by RCP. Return the archive entry for the package and store the package in `package-build-archive-dir'." (let* ((source-dir (package-recipe--working-tree rcp)) (file-specs (package-build--config-file-list rcp)) (files (package-build-expand-file-specs source-dir file-specs)) (commit (package-build--get-commit rcp)) (name (oref rcp name))) (unless (equal file-specs package-build-default-files-spec) (when (equal files (package-build-expand-file-specs source-dir package-build-default-files-spec nil t)) (package-build--message "Note: %s :files spec is equivalent to the default." name))) (cond ((not version) (error "Unable to check out repository for %s" name)) ((= 1 (length files)) (package-build--build-single-file-package rcp version commit (caar files) source-dir)) ((< 1 (length files)) (package-build--build-multi-file-package rcp version commit files source-dir)) (t (error "Unable to find files matching recipe patterns"))))) (define-obsolete-function-alias 'package-build-package 'package-build--package "Package-Build 2.0. The purpose of this alias is to get Cask working again. This alias is only a temporary kludge and is going to be removed again. It will likely be replaced by a function with the same name but a different signature. Do not use this alias elsewhere.") (defun package-build--build-single-file-package (rcp version commit file source-dir) (let* ((name (oref rcp name)) (pkg-source (expand-file-name file source-dir)) (pkg-target (expand-file-name (concat name "-" version ".el") package-build-archive-dir)) (pkg-info (package-build--merge-package-info (package-build--get-package-info pkg-source) name version commit))) (unless (string-equal (downcase (concat name ".el")) (downcase (file-name-nondirectory pkg-source))) (error "Single file %s does not match package name %s" (file-name-nondirectory pkg-source) name)) (copy-file pkg-source pkg-target t) (let ((enable-local-variables nil) (make-backup-files nil)) (with-current-buffer (find-file pkg-target) (package-build--update-or-insert-header "Package-Commit" commit) (package-build--update-or-insert-header "Package-Version" version) (package-build--ensure-ends-here-line pkg-source) (write-file pkg-target nil) (condition-case err (package-build--package-buffer-info-vec) (error (package-build--message "Warning: %S" err))) (kill-buffer))) (package-build--write-pkg-readme package-build-archive-dir (package-build--find-package-commentary pkg-source) name) (package-build--write-archive-entry rcp pkg-info 'single))) (defun package-build--build-multi-file-package (rcp version commit files source-dir) (let* ((name (oref rcp name)) (tmp-dir (file-name-as-directory (make-temp-file name t)))) (unwind-protect (let* ((pkg-dir-name (concat name "-" version)) (pkg-tmp-dir (expand-file-name pkg-dir-name tmp-dir)) (pkg-file (concat name "-pkg.el")) (pkg-file-source (or (car (rassoc pkg-file files)) pkg-file)) (file-source (concat name ".el")) (pkg-source (or (car (rassoc file-source files)) file-source)) (pkg-info (package-build--merge-package-info (let ((default-directory source-dir)) (or (package-build--get-pkg-file-info pkg-file-source) ;; Some packages provide NAME-pkg.el.in (package-build--get-pkg-file-info (expand-file-name (concat pkg-file ".in") (file-name-directory pkg-source))) (package-build--get-package-info pkg-source))) name version commit))) (package-build--copy-package-files files source-dir pkg-tmp-dir) (package-build--write-pkg-file (expand-file-name pkg-file (file-name-as-directory pkg-tmp-dir)) pkg-info) (package-build--generate-info-files files source-dir pkg-tmp-dir) (package-build--generate-dir-file files pkg-tmp-dir) (let ((default-directory tmp-dir)) (package-build--create-tar (expand-file-name (concat name "-" version ".tar") package-build-archive-dir) pkg-dir-name)) (let ((default-directory source-dir)) (package-build--write-pkg-readme package-build-archive-dir (package-build--find-package-commentary pkg-source) name)) (package-build--write-archive-entry rcp pkg-info 'tar)) (delete-directory tmp-dir t nil)))) ;;;###autoload (defun package-build-all () "Build a package for each of the available recipes." (interactive) (let* ((recipes (package-recipe-recipes)) (total (length recipes)) (success 0) invalid failed) (dolist (name recipes) (let ((rcp (with-demoted-errors (package-recipe-lookup name)))) (if rcp (if (with-demoted-errors (package-build-archive name) t) (cl-incf success) (push name failed)) (push name invalid)))) (if (not (or invalid failed)) (message "Successfully built all %s packages" total) (message "Successfully built %i of %s packages" success total) (when invalid (message "Did not built packages for %i invalid recipes:\n%s" (length invalid) (mapconcat (lambda (n) (concat " " n)) invalid "\n"))) (when failed (message "Building %i packages failed:\n%s" (length failed) (mapconcat (lambda (n) (concat " " n)) failed "\n"))))) (package-build-cleanup)) (defun package-build-cleanup () "Remove previously built packages that no longer have recipes." (interactive) (package-build-dump-archive-contents)) ;;; Archive (defun package-build-archive-alist () "Return the archive contents, without updating it first." (let ((file (expand-file-name "archive-contents" package-build-archive-dir))) (and (file-exists-p file) (with-temp-buffer (insert-file-contents file) (cdr (read (current-buffer))))))) (defun package-build-dump-archive-contents (&optional file pretty-print) "Update and return the archive contents. If non-nil, then store the archive contents in FILE instead of in the \"archive-contents\" file inside `package-build-archive-dir'. If PRETTY-PRINT is non-nil, then pretty-print instead of using one line per entry." (let (entries) (dolist (file (sort (directory-files package-build-archive-dir t ".*\.entry$") ;; Sort more recently-build packages first (lambda (f1 f2) (let ((default-directory package-build-archive-dir)) (file-newer-than-file-p f1 f2))))) (let* ((entry (with-temp-buffer (insert-file-contents file) (read (current-buffer)))) (name (car entry)) (newer-entry (assq name entries))) (if (not (file-exists-p (expand-file-name (symbol-name name) package-build-recipes-dir))) (package-build--remove-archive-files entry) ;; Prefer the more-recently-built package, which may not ;; necessarily have the highest version number, e.g. if ;; commit histories were changed. (if newer-entry (package-build--remove-archive-files entry) (push entry entries))))) (setq entries (sort entries (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))) (with-temp-file (or file (expand-file-name "archive-contents" package-build-archive-dir)) (let ((print-level nil) (print-length nil)) (if pretty-print (pp (cons 1 entries) (current-buffer)) (insert "(1") (dolist (entry entries) (newline) (insert " ") (prin1 entry (current-buffer))) (insert ")")))) entries)) (defalias 'package-build--archive-entries 'package-build-dump-archive-contents) (defun package-build--remove-archive-files (archive-entry) "Remove the entry and archive file for ARCHIVE-ENTRY." (package-build--message "Removing archive: %s-%s" (car archive-entry) (package-version-join (aref (cdr archive-entry) 0))) (let ((file (package-build--artifact-file archive-entry))) (when (file-exists-p file) (delete-file file))) (let ((file (package-build--archive-entry-file archive-entry))) (when (file-exists-p file) (delete-file file)))) ;;; Exporting Data as Json (defun package-build-recipe-alist-as-json (file) "Dump the recipe list to FILE as json." (interactive) (with-temp-file file (insert (json-encode (cl-mapcan (lambda (name) (ignore-errors ; Silently ignore corrupted recipes. (and (package-recipe-lookup name) (with-temp-buffer (insert-file-contents (expand-file-name name package-build-recipes-dir)) (let ((exp (read (current-buffer)))) (when (plist-member (cdr exp) :files) (plist-put (cdr exp) :files (format "%S" (plist-get (cdr exp) :files)))) (list exp)))))) (package-recipe-recipes)))))) (defun package-build--pkg-info-for-json (info) "Convert INFO into a data structure which will serialize to JSON in the desired shape." (let ((ver (elt info 0)) (deps (elt info 1)) (desc (elt info 2)) (type (elt info 3)) (props (and (> (length info) 4) (elt info 4)))) (list :ver ver :deps (cl-mapcan (lambda (dep) (list (intern (format ":%s" (car dep))) (cadr dep))) deps) :desc desc :type type :props props))) (defun package-build--archive-alist-for-json () "Return the archive alist in a form suitable for JSON encoding." (cl-flet ((format-person (person) (let ((name (car person)) (mail (cdr person))) (if (and name mail) (format "%s <%s>" name mail) (or name (format "<%s>" mail)))))) (cl-mapcan (lambda (entry) (list (intern (format ":%s" (car entry))) (let* ((info (cdr entry)) (extra (aref info 4)) (maintainer (assq :maintainer extra)) (authors (assq :authors extra))) (when maintainer (setcdr maintainer (format-person (cdr maintainer)))) (when authors (if (cl-every #'listp (cdr authors)) (setcdr authors (mapcar #'format-person (cdr authors))) (assq-delete-all :authors extra))) (package-build--pkg-info-for-json info)))) (package-build-archive-alist)))) (defun package-build-archive-alist-as-json (file) "Dump the build packages list to FILE as json." (with-temp-file file (insert (json-encode (package-build--archive-alist-for-json))))) ;;; Backports (defun package-build--lm-homepage (&optional file) "Return the homepage in file FILE, or current buffer if FILE is nil. This is a copy of `lm-homepage', which first appeared in Emacs 24.4." (let ((page (lm-with-file file (lm-header "\\(?:x-\\)?\\(?:homepage\\|url\\)")))) (if (and page (string-match "^<.+>$" page)) (substring page 1 -1) page))) ;;; _ (provide 'package-build) ;; For the time being just require all libraries that contain code ;; that was previously located in this library. (require 'package-build-badges) (require 'package-recipe-mode) ;; Local Variables: ;; coding: utf-8 ;; checkdoc-minor-mode: 1 ;; indent-tabs-mode: nil ;; End: ;;; package-build.el ends here ================================================ FILE: lisp/extern/package-build/24/package-recipe-mode.el ================================================ ;;; package-recipe-mode.el --- Minor mode for editing package recipes ;; Copyright (C) 2011-2020 Donald Ephraim Curtis ;; Copyright (C) 2012-2020 Steve Purcell ;; Copyright (C) 2016-2020 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Keywords: tools ;; This file is not (yet) part of GNU Emacs. ;; However, it is distributed under the same license. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This library defines the minor mode `package-build-minor-mode', ;; which will likely be replaced with the `emacs-lisp-mode' derived ;; `package-recipe-mode' eventually. ;;; Code: (require 'package-build) (defvar package-build-minor-mode-map (let ((m (make-sparse-keymap))) (define-key m (kbd "C-c C-c") 'package-build-current-recipe) m) "Keymap for `package-build-minor-mode'.") (define-minor-mode package-build-minor-mode "Helpful functionality for building packages." nil " PBuild" package-build-minor-mode-map (when package-build-minor-mode (message "Use C-c C-c to build this recipe."))) ;;;###autoload (defun package-build-create-recipe (name fetcher) "Create a new recipe for the package named NAME using FETCHER." (interactive (list (read-string "Package name: ") (intern (completing-read "Fetcher: " (list "git" "github" "gitlab" "hg") nil t nil nil "github")))) (let ((recipe-file (expand-file-name name package-build-recipes-dir))) (when (file-exists-p recipe-file) (error "Recipe already exists")) (find-file recipe-file) (insert (pp-to-string `(,(intern name) :fetcher ,fetcher ,@(cl-case fetcher (github (list :repo "USER/REPO")) (t (list :url "SCM_URL_HERE")))))) (emacs-lisp-mode) (package-build-minor-mode) (goto-char (point-min)))) ;;;###autoload (defun package-build-current-recipe () "Build archive for the recipe defined in the current buffer." (interactive) (unless (and (buffer-file-name) (file-equal-p (file-name-directory (buffer-file-name)) package-build-recipes-dir)) (error "Buffer is not visiting a recipe")) (when (buffer-modified-p) (if (y-or-n-p (format "Save file %s? " buffer-file-name)) (save-buffer) (error "Aborting"))) (check-parens) (let ((name (file-name-nondirectory (buffer-file-name)))) (package-build-archive name t) (let ((output-buffer-name "*package-build-result*")) (with-output-to-temp-buffer output-buffer-name (princ ";; Please check the following package descriptor.\n") (princ ";; If the correct package description or dependencies are missing,\n") (princ ";; then the source .el file is likely malformed, and should be fixed.\n") (pp (assoc (intern name) (package-build-archive-alist)))) (with-current-buffer output-buffer-name (emacs-lisp-mode) (view-mode))) (when (yes-or-no-p "Install new package? ") (package-install-file (package-build--artifact-file (assq (intern name) (package-build-archive-alist))))))) (provide 'package-recipe-mode) ;; Local Variables: ;; coding: utf-8 ;; checkdoc-minor-mode: 1 ;; indent-tabs-mode: nil ;; End: ;;; package-recipe-mode.el ends here ================================================ FILE: lisp/extern/package-build/24/package-recipe.el ================================================ ;;; package-recipe.el --- Package recipes as EIEIO objects -*- lexical-binding: t -*- ;; Copyright (C) 2018-2020 Jonas Bernoulli ;; Author: Jonas Bernoulli ;; This file is not (yet) part of GNU Emacs. ;; However, it is distributed under the same license. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Package recipes as EIEIO objects. ;;; Code: (require 'eieio) (defvar package-build-recipes-dir) (defvar package-build-working-dir) ;;; Classes (defclass package-recipe () ((url-format :allocation :class :initform nil) (repopage-format :allocation :class :initform nil) (tag-regexp :allocation :class :initform nil) (stable-p :allocation :class :initform nil) (name :initarg :name :initform nil) (url :initarg :url :initform nil) (repo :initarg :repo :initform nil) (repopage :initarg :repopage :initform nil) (files :initarg :files :initform nil) (branch :initarg :branch :initform nil) (commit :initarg :commit :initform nil) (version-regexp :initarg :version-regexp :initform nil) (old-names :initarg :old-names :initform nil)) :abstract t) (defmethod package-recipe--working-tree ((rcp package-recipe)) (file-name-as-directory (expand-file-name (oref rcp name) package-build-working-dir))) (defmethod package-recipe--upstream-url ((rcp package-recipe)) (or (oref rcp url) (format (oref rcp url-format) (oref rcp repo)))) ;;;; Git (defclass package-git-recipe (package-recipe) ((tag-regexp :initform "\ \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} \ [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)"))) (defclass package-github-recipe (package-git-recipe) ((url-format :initform "https://github.com/%s.git") (repopage-format :initform "https://github.com/%s"))) (defclass package-gitlab-recipe (package-git-recipe) ((url-format :initform "https://gitlab.com/%s.git") (repopage-format :initform "https://gitlab.com/%s"))) ;;;; Mercurial (defclass package-hg-recipe (package-recipe) ((tag-regexp :initform "\ \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} \ [0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)"))) ;;; Interface (defun package-recipe-recipes () "Return a list of the names of packages with available recipes." (directory-files package-build-recipes-dir nil "^[^.]")) (defun package-recipe-read-name () "Read the name of a package for which a recipe is available." (completing-read "Package: " (package-recipe-recipes))) (defun package-recipe-lookup (name) "Return a recipe object for the package named NAME. If no such recipe file exists or if the contents of the recipe file is invalid, then raise an error." (let ((file (expand-file-name name package-build-recipes-dir))) (if (file-exists-p file) (let* ((recipe (with-temp-buffer (insert-file-contents file) (read (current-buffer)))) (plist (cdr recipe)) (fetcher (plist-get plist :fetcher)) key val args) (package-recipe--validate recipe name) (while (and (setq key (pop plist)) (setq val (pop plist))) (unless (eq key :fetcher) (push val args) (push key args))) (apply (intern (format "package-%s-recipe" fetcher)) name :name name args)) (error "No such recipe: %s" name)))) ;;; Validation (defun package-recipe--validate (recipe name) "Perform some basic checks on the raw RECIPE for the package named NAME." (pcase-let ((`(,ident . ,plist) recipe)) (cl-assert ident) (cl-assert (symbolp ident)) (cl-assert (string= (symbol-name ident) name) nil "Recipe '%s' contains mismatched package name '%s'" name ident) (cl-assert plist) (let* ((symbol-keys '(:fetcher)) (string-keys '(:url :repo :commit :branch :version-regexp)) (list-keys '(:files :old-names)) (all-keys (append symbol-keys string-keys list-keys))) (dolist (thing plist) (when (keywordp thing) (cl-assert (memq thing all-keys) nil "Unknown keyword %S" thing))) (let ((fetcher (plist-get plist :fetcher))) (cl-assert fetcher nil ":fetcher is missing") (if (memq fetcher '(github gitlab)) (progn (cl-assert (plist-get plist :repo) ":repo is missing") (cl-assert (not (plist-get plist :url)) ":url is redundant")) (cl-assert (plist-get plist :url) ":url is missing"))) (dolist (key symbol-keys) (let ((val (plist-get plist key))) (when val (cl-assert (symbolp val) nil "%s must be a symbol but is %S" key val)))) (dolist (key list-keys) (let ((val (plist-get plist key))) (when val (cl-assert (listp val) nil "%s must be a list but is %S" key val)))) (dolist (key string-keys) (let ((val (plist-get plist key))) (when val (cl-assert (stringp val) nil "%s must be a string but is %S" key val))))) recipe)) ;;; _ (provide 'package-recipe) ;; Local Variables: ;; coding: utf-8 ;; checkdoc-minor-mode: 1 ;; indent-tabs-mode: nil ;; End: ;;; package-recipe.el ends here ================================================ FILE: lisp/extern/package-build/25/package-build-badges.el ================================================ ;;; package-build-badges.el --- Create batches for packages -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2011-2023 Donald Ephraim Curtis ;; Copyright (C) 2012-2023 Steve Purcell ;; Copyright (C) 2018-2023 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; In future we should provide a hook. Note also that it would be ;; straightforward to generate the SVG ourselves, which would save ;; the network overhead. ;;; Code: (defvar package-build-stable) (defun package-build--write-melpa-badge-image (name version target-dir) (unless (zerop (call-process "curl" nil nil nil "-f" "-o" (expand-file-name (concat name "-badge.svg") target-dir) (format "https://img.shields.io/badge/%s-%s-%s.svg" (if package-build-stable "melpa stable" "melpa") (url-hexify-string version) (if package-build-stable "3e999f" "922793")))) (message "Failed to fetch badge"))) (provide 'package-build-badges) ;;; package-badges.el ends here ================================================ FILE: lisp/extern/package-build/25/package-build.el ================================================ ;;; package-build.el --- Tools for assembling a package archive -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2011-2023 Donald Ephraim Curtis ;; Copyright (C) 2012-2023 Steve Purcell ;; Copyright (C) 2016-2023 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Steve Purcell ;; Jonas Bernoulli ;; Phil Hagelberg ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; Package-Version: 4.0.0.50-git ;; Package-Requires: ((emacs "25.1")) ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; This file allows a curator to publish an archive of Emacs packages. ;; The archive is generated from a set of recipes, which describe elisp ;; projects and repositories from which to get them. The term "package" ;; here is used to mean a specific version of a project that is prepared ;; for download and installation. ;;; Code: (require 'cl-lib) (require 'pcase) (require 'subr-x) (require 'package) (require 'lisp-mnt) (require 'json) (require 'package-recipe) (require 'package-build-badges) ;;; Options (defvar package-build--melpa-base (file-name-directory (directory-file-name (file-name-directory (or load-file-name (buffer-file-name)))))) (defgroup package-build nil "Tools for building package.el-compliant packages from upstream source code." :group 'development) (defcustom package-build-working-dir (expand-file-name "working/" package-build--melpa-base) "Directory in which to keep checkouts." :group 'package-build :type 'string) (defcustom package-build-archive-dir (expand-file-name "packages/" package-build--melpa-base) "Directory in which to keep compiled archives." :group 'package-build :type 'string) (defcustom package-build-recipes-dir (expand-file-name "recipes/" package-build--melpa-base) "Directory containing recipe files." :group 'package-build :type 'string) (defcustom package-build-verbose t "When non-nil, then print additional progress information." :group 'package-build :type 'boolean) (defcustom package-build-stable nil "When non-nil, then try to build packages from versions-tagged code." :group 'package-build :type 'boolean) (defcustom package-build-get-version-function (if package-build-stable 'package-build-get-tag-version 'package-build-get-timestamp-version) "The function used to determine the commit and version of a package. The default depends on the value of option `package-build-stable'. This function is called with one argument, the recipe object, and must return (COMMIT TIME VERSION), where COMMIT is the commit choosen by the function, TIME is its commit date, and VERSION is the version string choosen for COMMIT." :group 'package-build :set-after '(package-build-stable) :type 'function) (defcustom package-build-predicate-function nil "Predicate used by `package-build-all' to determine which packages to build. If non-nil, this function is called with the recipe object as argument, and must return non-nil if the package is to be build. If nil (the default), then all packages are build." :group 'package-build :type '(choice (const :tag "build all") function)) (defcustom package-build-build-function nil "Low-level function used to build a package. If nil (the default) then the funcion used depends on whether the package consists of more than one file or not. One possible value is `package-build--build-multi-file-package', which would force building a tarball, even for packages that consist of a single file." :group 'package-build :type '(choice (const :tag "default, depending on number of files") function)) ;; NOTE that these hooks are still experimental. Let me know if these ;; are potentially useful for you and whether any changes are required ;; to make them more appropriate for your usecase. (defvar package-build-worktree-function #'package-recipe--working-tree) (defvar package-build-early-worktree-function #'package-recipe--working-tree) (defvar package-build-fetch-function #'package-build--fetch) (defvar package-build-checkout-function #'package-build--checkout) (defvar package-build-cleanup-function #'package-build--cleanup) (defcustom package-build-timeout-executable "timeout" "Path to a GNU coreutils \"timeout\" command if available. This must be a version which supports the \"-k\" option. On MacOS it is possible to install coreutils using Homebrew or similar, which will provide the GNU timeout program as \"gtimeout\"." :group 'package-build :type '(file :must-match t)) (defcustom package-build-timeout-secs nil "Wait this many seconds for external processes to complete. If an external process takes longer than specified here to complete, then it is terminated. If nil, then no time limit is applied. This setting requires `package-build-timeout-executable' to be set." :group 'package-build :type 'number) (defcustom package-build-tar-executable "tar" "Path to a (preferably GNU) tar command. Certain package names (e.g. \"@\") may not work properly with a BSD tar. On MacOS it is possible to install coreutils using Homebrew or similar, which will provide the GNU timeout program as \"gtar\"." :group 'package-build :type '(file :must-match t)) (defcustom package-build-write-melpa-badge-images nil "When non-nil, write MELPA badge images alongside packages. These batches can, for example, be used on GitHub pages." :group 'package-build :type 'boolean) (defcustom package-build-version-regexp "^[rRvV]?\\(.*\\)$" "Default pattern for matching valid version-strings within repository tags. The string in the capture group should be parsed as valid by `version-to-list'." :group 'package-build :type 'string) (defcustom package-build-allowed-git-protocols '("https" "file" "ssh") "Protocols that can be used to fetch from upstream with git. By default insecure protocols, such as \"http\" or \"git\", are disallowed." :group 'package-build :type '(repeat string)) (defvar package-build-use-git-remote-hg nil "Whether to use `git-remote-hg' remote helper for mercurial repos.") (defvar package-build--inhibit-fetch nil "Whether to inhibit fetching. Useful for testing purposes.") (defvar package-build--inhibit-checkout nil "Whether to inhibit checkout. Useful for testing purposes.") ;;; Generic Utilities (defun package-build--message (format-string &rest args) "Behave like `message' if `package-build-verbose' is non-nil. Otherwise do nothing. FORMAT-STRING and ARGS are as per that function." (when package-build-verbose (apply #'message format-string args))) ;;; Version Handling ;;;; Common (defun package-build--select-version (rcp) (pcase-let* ((default-directory (package-build--working-tree rcp t)) (`(,commit ,time ,version) (funcall package-build-get-version-function rcp))) (unless version (error "Cannot detect version for %s" (oref rcp name))) (oset rcp commit commit) (oset rcp time time) (oset rcp version version))) (cl-defmethod package-build--select-commit ((rcp package-git-recipe) rev exact) (pcase-let* ((`(,hash ,time) (split-string (car (apply #'process-lines "git" "log" "-n1" "--first-parent" "--pretty=format:%H %cd" "--date=unix" rev (and (not exact) (cons "--" (package-build--spec-globs rcp))))) " "))) (list hash (string-to-number time)))) (cl-defmethod package-build--select-commit ((rcp package-hg-recipe) rev exact) (pcase-let* ((`(,hash ,time ,_timezone) (split-string (car (apply #'process-lines ;; The "date" keyword uses UTC. The "hgdate" filter ;; returns two integers separated by a space; the ;; unix timestamp and the timezone offset. We use ;; "hgdate" because that makes it easier to discard ;; the time zone offset, which doesn't interest us. "hg" "log" "--limit" "1" "--template" "{node} {date|hgdate}\n" "--rev" rev (and (not exact) (cons "--" (package-build--spec-globs rcp))))) " "))) (list hash (string-to-number time)))) ;;;; Release (defun package-build-get-tag-version (rcp) (let ((regexp (or (oref rcp version-regexp) package-build-version-regexp)) (tag nil) (version '(0))) (dolist (n (cl-etypecase rcp (package-git-recipe (process-lines "git" "tag" "--list")) (package-hg-recipe (process-lines "hg" "tags" "--quiet")))) (let ((v (ignore-errors (version-to-list (and (string-match regexp n) (match-string 1 n)))))) (when (and v (version-list-<= version v)) (if (cl-typep rcp 'package-git-recipe) (setq tag (concat "refs/tags/" n)) (setq tag n)) (setq version v)))) (and tag (pcase-let ((`(,hash ,time) (package-build--select-commit rcp tag t))) (list hash time (package-version-join version)))))) ;;;; Snapshot (defun package-build-get-timestamp-version (rcp) (pcase-let ((`(,hash ,time) (package-build--get-timestamp-version rcp))) (list hash time ;; We remove zero-padding of the HH portion, as ;; that is lost when stored in archive-contents. (concat (format-time-string "%Y%m%d." time t) (format "%d" (string-to-number (format-time-string "%H%M" time t))))))) (cl-defmethod package-build--get-timestamp-version ((rcp package-git-recipe)) (pcase-let* ((commit (oref rcp commit)) (branch (oref rcp branch)) (branch (and branch (concat "origin/" branch))) (rev (or commit branch "origin/HEAD")) (`(,rev-hash ,rev-time) (package-build--select-commit rcp rev commit)) (`(,tag-hash ,tag-time) (package-build-get-tag-version rcp))) ;; If the latest commit that touches a relevant file is an ancestor of ;; the latest tagged release and the tag is reachable from origin/HEAD ;; (i.e., it isn't on a separate release branch) then use the tagged ;; release. Snapshots should not be older than the latest release. (if (and tag-hash (zerop (call-process "git" nil nil nil "merge-base" "--is-ancestor" rev-hash tag-hash)) (zerop (call-process "git" nil nil nil "merge-base" "--is-ancestor" tag-hash rev))) (list tag-hash tag-time) (list rev-hash rev-time)))) (cl-defmethod package-build--get-timestamp-version ((rcp package-hg-recipe)) ;; TODO Respect commit and branch properties. ;; TODO Use latest release if appropriate. (package-build--select-commit rcp "." nil)) ;;; Run Process (defun package-build--run-process (command &rest args) "Run COMMAND with ARGS in `default-directory'. We use this to wrap commands is proper environment settings and with a timeout so that no command can block the build process." (unless (file-directory-p default-directory) (error "Cannot run process in non-existent directory: %s" default-directory)) (with-temp-buffer (pcase-let* ((`(,command . ,args) (nconc (and (not (eq system-type 'windows-nt)) (list "env" "LC_ALL=C")) (if (and package-build-timeout-secs package-build-timeout-executable) (nconc (list package-build-timeout-executable "-k" "60" (number-to-string package-build-timeout-secs) command) args) (cons command args)))) (exit-code (apply #'call-process command nil (current-buffer) nil args))) (unless (zerop exit-code) (message "\nCommand '%s' exited with non-zero exit-code: %d\n" (mapconcat #'shell-quote-argument argv " ") exit-code) (message "%s" (buffer-string)) (error "Command exited with non-zero exit-code: %d" exit-code))))) ;;; Worktree (defun package-build--working-tree (rcp &optional early) (if early (funcall package-build-early-worktree-function rcp) (funcall package-build-worktree-function rcp))) ;;; Fetch (cl-defmethod package-build--fetch ((rcp package-git-recipe)) (let ((dir (package-build--working-tree rcp t)) (url (package-recipe--upstream-url rcp)) (protocol (package-recipe--upstream-protocol rcp))) (unless (member protocol package-build-allowed-git-protocols) (error "Fetching using the %s protocol is not allowed" protocol)) (cond ((and (file-exists-p (expand-file-name ".git" dir)) (let ((default-directory dir)) (string= (car (process-lines "git" "config" "remote.origin.url")) url))) (unless package-build--inhibit-fetch (let ((default-directory dir)) (package-build--message "Updating %s" dir) (package-build--run-process "git" "fetch" "-f" "--all" "--tags") ;; We might later checkout "origin/HEAD". Sadly "git fetch" ;; cannot be told to keep it up-to-date, so we have to make ;; a second request. (package-build--run-process "git" "remote" "set-head" "origin" "--auto")))) (t (when (file-exists-p dir) (delete-directory dir t)) (package-build--message "Cloning %s to %s" url dir) (let ((default-directory package-build-working-dir)) (apply #'package-build--run-process "git" "clone" url dir ;; This can dramatically reduce the size of large repos. ;; But we can only do this when using a version function ;; that is known not to require a checkout and history. ;; See #52. (and (eq package-build-get-version-function #'package-build-get-tag-version) (list "--filter=blob:none" "--no-checkout")))))))) (cl-defmethod package-build--fetch ((rcp package-hg-recipe)) (let ((dir (package-build--working-tree rcp t)) (url (package-recipe--upstream-url rcp))) (cond ((and (file-exists-p (expand-file-name ".hg" dir)) (let ((default-directory dir)) (string= (car (process-lines "hg" "paths" "default")) url))) (unless package-build--inhibit-fetch (let ((default-directory dir)) (package-build--message "Updating %s" dir) (package-build--run-process "hg" "pull") (package-build--run-process "hg" "update")))) (t (when (file-exists-p dir) (delete-directory dir t)) (package-build--message "Cloning %s to %s" url dir) (let ((default-directory package-build-working-dir)) (package-build--run-process "hg" "clone" url dir)))))) ;;; Checkout (cl-defmethod package-build--checkout ((rcp package-git-recipe)) (unless package-build--inhibit-checkout (let ((rev (oref rcp commit))) (package-build--message "Checking out %s" rev) (package-build--run-process "git" "reset" "--hard" rev)))) (cl-defmethod package-build--checkout ((rcp package-hg-recipe)) (unless package-build--inhibit-checkout (let ((rev (oref rcp commit))) (package-build--message "Checking out %s" rev) (package-build--run-process "hg" "update" rev)))) ;;; Generate Files (defun package-build--write-pkg-file (desc dir) (let ((name (package-desc-name desc))) (with-temp-file (expand-file-name (format "%s-pkg.el" name) dir) (pp `(define-package ,(symbol-name name) ,(package-version-join (package-desc-version desc)) ,(package-desc-summary desc) ',(mapcar (pcase-lambda (`(,pkg ,ver)) (list pkg (package-version-join ver))) (package-desc-reqs desc)) ,@(cl-mapcan (pcase-lambda (`(,key . ,val)) (when (or (symbolp val) (listp val)) ;; We must quote lists and symbols, ;; because Emacs 24.3 and earlier evaluate ;; the package information, which would ;; break for unquoted symbols or lists. ;; While this library does not support ;; such old Emacsen, the packages that ;; we produce should remain compatible. (setq val (list 'quote val))) (list key val)) (package-desc-extras desc))) (current-buffer)) (princ ";; Local Variables:\n;; no-byte-compile: t\n;; End:\n" (current-buffer))))) (defun package-build--create-tar (rcp directory) "Create a tar file containing the package version specified by RCP. DIRECTORY is a temporary directory that contains the directory that is put in the tarball." (let* ((name (oref rcp name)) (version (oref rcp version)) (time (oref rcp time)) (tar (expand-file-name (concat name "-" version ".tar") package-build-archive-dir)) (dir (concat name "-" version))) (when (eq system-type 'windows-nt) (setq tar (replace-regexp-in-string "^\\([a-z]\\):" "/\\1" tar))) (let ((default-directory directory)) (process-file package-build-tar-executable nil (get-buffer-create "*package-build-checkout*") nil "-cf" tar dir ;; Arguments that are need to strip metadata that ;; prevent a reproducable tarball as described at ;; https://reproducible-builds.org/docs/archives. "--sort=name" (format "--mtime=@%d" time) "--owner=0" "--group=0" "--numeric-owner" "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime")) (when (and package-build-verbose noninteractive) (message "Created %s containing:" (file-name-nondirectory tar)) (dolist (line (sort (process-lines package-build-tar-executable "--list" "--file" tar) #'string<)) (message " %s" line))))) (defun package-build--write-pkg-readme (pkg files) (when-let* ((name (oref pkg name)) (commentary (let* ((file (concat name ".el")) (file (or (car (rassoc file files)) file)) (file (and file (expand-file-name file)))) (and (file-exists-p file) (lm-commentary file))))) (with-temp-buffer (if (>= emacs-major-version 28) (insert commentary) ;; Taken from 28.0's `lm-commentary'. (insert (replace-regexp-in-string ; Get rid of... "[[:blank:]]*$" "" ; trailing white-space (replace-regexp-in-string (format "%s\\|%s\\|%s" ;; commentary header (concat "^;;;[[:blank:]]*\\(" lm-commentary-header "\\):[[:blank:]\n]*") "^;;[[:blank:]]?" ; double semicolon prefix "[[:blank:]\n]*\\'") ; trailing new-lines "" commentary)))) (unless (or (bobp) (= (char-before) ?\n)) (insert ?\n)) ;; We write the file even if it is empty, which is perhaps ;; a questionable choice, but at least it's consistent. (let ((coding-system-for-write buffer-file-coding-system)) (write-region nil nil (expand-file-name (concat name "-readme.txt") package-build-archive-dir)))))) (defun package-build--generate-info-files (files target-dir) "Create an info file for each texinfo file listed in FILES. Also create the info dir file. Remove each original texinfo file. The source and destination file paths are expanded in `default-directory' and TARGET-DIR respectively." (pcase-dolist (`(,src . ,tmp) files) (let ((extension (file-name-extension tmp))) (when (member extension '("info" "texi" "texinfo")) (let* ((src (expand-file-name src)) (tmp (expand-file-name tmp target-dir)) (texi src) (info tmp)) (when (member extension '("texi" "texinfo")) (delete-file tmp) (setq info (concat (file-name-sans-extension tmp) ".info")) (unless (file-exists-p info) (package-build--message "Generating %s" info) ;; If the info file is located in a subdirectory ;; and contains relative includes, then it is ;; necessary to run makeinfo in the subdirectory. (with-demoted-errors "Error: %S" (let ((default-directory (file-name-directory texi))) (package-build--run-process "makeinfo" "--no-split" texi "-o" info))))) (with-demoted-errors "Error: %S" (let ((default-directory target-dir)) (package-build--run-process "install-info" "--dir=dir" info)))))))) ;;; Patch Libraries (defun package-build--update-or-insert-header (name value) "Ensure current buffer has NAME header with the given VALUE. Any existing header will be preserved and given the \"X-Original-\" prefix. If VALUE is nil, the new header will not be inserted, but any original will still be renamed." (goto-char (point-min)) (cond ((let ((case-fold-search t)) (re-search-forward (format "^;+* *%s *: *" (regexp-quote name)) nil t)) (move-beginning-of-line nil) (search-forward "V" nil t) (backward-char) (insert "X-Original-") (move-beginning-of-line nil)) (t ;; Put the new header in a sensible place if we can. (re-search-forward "^;+* *\\(Version\\|Package-Requires\\|Keywords\\|URL\\) *:" nil t) (forward-line))) (insert (format ";; %s: %s\n" name value))) (defun package-build--ensure-ends-here-line (file) "Add the \"FILE ends here\" trailing line if it is missing." (save-excursion (goto-char (point-min)) (let ((trailer (format ";;; %s ends here" (file-name-nondirectory file)))) (unless (re-search-forward (format "^%s" (regexp-quote trailer)) nil t) (goto-char (point-max)) (insert ?\n trailer ?\n))))) ;;; Package Structs (defun package-build--desc-from-library (rcp files &optional kind) "Return the package description for RCP. This function is used for all packages that consist of a single file and those packages that consist of multiple files but lack a file named \"NAME-pkg.el\" or \"NAME-pkg.el\". The returned value is a `package-desc' struct (which see). The values of the `name' and `version' slots are taken from RCP itself. The value of `kind' is taken from the KIND argument, which defaults to `single'; the other valid value being `tar'. Other information is taken from the file named \"NAME-pkg.el\", which should appear in FILES. As a fallback, \"NAME-pkg.el.in\" is also tried. If neither file exists, then return nil. If a value is not specified in the used file, then fall back to the value specified in the file \"NAME.el\"." (let* ((name (oref rcp name)) (version (oref rcp version)) (commit (oref rcp commit)) (file (concat name ".el")) (file (or (car (rassoc file files)) file))) (and (file-exists-p file) (with-temp-buffer (insert-file-contents file) (package-desc-from-define name version (or (save-excursion (goto-char (point-min)) (and (re-search-forward "^;;; [^ ]*\\.el ---[ \t]*\\(.*?\\)[ \t]*\\(-\\*-.*-\\*-[ \t]*\\)?$" nil t) (match-string-no-properties 1))) "No description available.") (when-let* ((require-lines (lm-header-multiline "package-requires"))) (package--prepare-dependencies (package-read-from-string (mapconcat #'identity require-lines " ")))) :kind (or kind 'single) :url (lm-homepage) :keywords (lm-keywords-list) :maintainer (if (fboundp 'lm-maintainers) (car (lm-maintainers)) (with-no-warnings (lm-maintainer))) :authors (lm-authors) :commit commit))))) (defun package-build--desc-from-package (rcp files) "Return the package description for RCP. This function is used for packages that consist of multiple files. The returned value is a `package-desc' struct (which see). The values of the `name' and `version' slots are taken from RCP itself. The value of `kind' is always `tar'. Other information is taken from the file named \"NAME.el\", which should appear in FILES. As a fallback, \"NAME.el.in\" is also tried. If neither file exists, then return nil." (let* ((name (oref rcp name)) (version (oref rcp version)) (commit (oref rcp commit)) (file (concat name "-pkg.el")) (file (or (car (rassoc file files)) file))) (and (or (file-exists-p file) (file-exists-p (setq file (concat file ".in")))) (let ((form (with-temp-buffer (insert-file-contents file) (read (current-buffer))))) (unless (eq (car form) 'define-package) (error "No define-package found in %s" file)) (pcase-let* ((`(,_ ,_ ,_ ,summary ,deps . ,extra) form) (deps (eval deps)) (alt-desc (package-build--desc-from-library rcp files)) (alt (and alt-desc (package-desc-extras alt-desc)))) (when (string-match "[\r\n]" summary) (error "Illegal multi-line package description in %s" file)) (package-desc-from-define name version (if (string-empty-p summary) (or (and alt-desc (package-desc-summary alt-desc)) "No description available.") summary) (mapcar (pcase-lambda (`(,pkg ,ver)) (unless (symbolp pkg) (error "Invalid package name in dependency: %S" pkg)) (list pkg ver)) deps) :kind 'tar :url (or (alist-get :url extra) (alist-get :homepage extra) (alist-get :url alt)) :keywords (or (alist-get :keywords extra) (alist-get :keywords alt)) :maintainer (or (alist-get :maintainer extra) (alist-get :maintainer alt)) :authors (or (alist-get :authors extra) (alist-get :authors alt)) :commit commit)))))) (defun package-build--write-archive-entry (desc) (with-temp-file (expand-file-name (concat (package-desc-full-name desc) ".entry") package-build-archive-dir) (pp (cons (package-desc-name desc) (vector (package-desc-version desc) (package-desc-reqs desc) (package-desc-summary desc) (package-desc-kind desc) (package-desc-extras desc))) (current-buffer)))) ;;; Files Spec (defconst package-build-default-files-spec '("*.el" "lisp/*.el" "dir" "*.info" "*.texi" "*.texinfo" "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo" "docs/dir" "docs/*.info" "docs/*.texi" "docs/*.texinfo" (:exclude ".dir-locals.el" "lisp/.dir-locals.el" "test.el" "tests.el" "*-test.el" "*-tests.el" "lisp/test.el" "lisp/tests.el" "lisp/*-test.el" "lisp/*-tests.el")) "Default value for :files attribute in recipes.") (defun package-build-expand-files-spec (rcp &optional assert repo spec) "Return an alist of files of package RCP to be included in tarball. Each element has the form (SOURCE . DESTINATION), where SOURCE is a file in the package's repository and DESTINATION is where that file is placed in the package's tarball. RCP is the package recipe as an object. If the `files' slot of RCP is non-nil, then that is used as the file specification. Otherwise `package-build-default-files-spec' is used. If optional ASSERT is non-nil, then raise an error if nil would be returned. If ASSERT and `files' are both non-nil and using `files' results in the same set of files as the default spec, then show a warning. A files specification is a list. Its elements are processed in order and can have the following form: - :defaults If the first element is `:defaults', then that means to prepend the default files spec to the SPEC specified by the remaining elements. - GLOB A string is glob-expanded to match zero or more files. Matched files are copied to the top-level directory. - (SUBDIRECTORY GLOB...) A list that begins with a string causes the files matched by the second and subsequent elements to be copied into the sub- directory specified by the first element. - (:exclude GLOB...) A list that begins with `:exclude' causes files that were matched by earlier elements that are also matched by the second and subsequent elements of this list to be removed from the returned alist. Files matched by later elements are not affected." (let ((default-directory (or repo (package-build--working-tree rcp))) (spec (or spec (oref rcp files)))) (when (eq (car spec) :defaults) (setq spec (append package-build-default-files-spec (cdr spec)))) (let ((files (package-build--expand-files-spec-1 (or spec package-build-default-files-spec)))) (when assert (when (and rcp spec (equal files (package-build--expand-files-spec-1 package-build-default-files-spec))) (message "Warning: %s :files spec is equivalent to the default" (oref rcp name))) (unless files (error "No matching file(s) found in %s using %s" default-directory (or spec "default spec")))) files))) (defun package-build--expand-files-spec-1 (spec) "Return a list of all files matching SPEC in `default-directory'. SPEC is a full files spec as stored in a recipe object." (let (include exclude) (dolist (entry spec) (if (eq (car-safe entry) :exclude) (dolist (entry (cdr entry)) (push entry exclude)) (push entry include))) (cl-set-difference (package-build--expand-files-spec-2 (nreverse include)) (package-build--expand-files-spec-2 (nreverse exclude)) :test #'equal :key #'car))) (defun package-build--expand-files-spec-2 (spec &optional subdir) "Return a list of all files matching SPEC in SUBDIR. If SUBDIR is nil, use `default-directory'. SPEC is expected to be a partial files spec, consisting of either all include rules or all exclude rules (with the `:exclude' keyword removed)." (mapcan (lambda (entry) (if (stringp entry) (mapcar (lambda (f) (cons f (concat subdir (replace-regexp-in-string "\\.el\\.in\\'" ".el" (file-name-nondirectory f))))) (file-expand-wildcards entry)) (package-build--expand-files-spec-2 (cdr entry) (concat subdir (car entry) "/")))) spec)) (defun package-build--copy-package-files (files target-dir) "Copy FILES from `default-directory' to TARGET-DIR. FILES is a list of (SOURCE . DEST) relative filepath pairs." (package-build--message "Copying files (->) and directories (=>)\n from %s\n to %s" default-directory target-dir) (pcase-dolist (`(,src . ,dst) files) (let ((src* (expand-file-name src)) (dst* (expand-file-name dst target-dir))) (make-directory (file-name-directory dst*) t) (cond ((file-regular-p src*) (package-build--message " %s %s -> %s" (if (equal src dst) " " "!") src dst) (copy-file src* dst*)) ((file-directory-p src*) (package-build--message " %s %s => %s" (if (equal src dst) " " "!") src dst) (copy-directory src* dst*)))))) (defun package-build--spec-globs (rcp) "Return a list of vcs arguments to match the files specified in RCP." ;; See glob(7), gitglossary(7) and "hg help patterns". (cl-flet ((toargs (glob &optional exclude) ;; Given an element like ("dir" "dir/*"), we want to move ;; all children of "dir" to the top-level. Glob handling ;; of git-log/hg-log only cares about regular file, so if ;; "dir/subdir/file" is modified, then "dir/*" does not ;; match that change. Use "dir/**" instead, to make them ;; look for changes to files in "dir" and all subdirs. (when (string-suffix-p "/*" glob) (setq glob (concat glob "*"))) (cl-etypecase rcp (package-git-recipe (list (format ":(glob%s)%s" (if exclude ",exclude" "") glob))) (package-hg-recipe (list (if exclude "--exclude" "--include") (concat "glob:" glob)))))) (mapcan (lambda (entry) (pcase-exhaustive entry ((and glob (pred stringp)) (toargs glob)) ((and `(:exclude . ,globs) (guard (cl-every #'stringp globs))) (mapcan (lambda (g) (toargs g t)) globs)) ((and `(,dir . ,globs) (guard (stringp dir)) (guard (cl-every #'stringp globs))) (mapcan #'toargs globs)))) (let ((spec (or (oref rcp files) package-build-default-files-spec))) (if (eq (car spec) :defaults) (append package-build-default-files-spec (cdr spec)) spec))))) ;;; Commands ;;;###autoload (defun package-build-archive (name &optional dump-archive-contents) "Build a package archive for the package named NAME. If DUMP-ARCHIVE-CONTENTS is non-nil, the updated archive contents are subsequently dumped." (interactive (list (package-recipe-read-name) t)) (unless (file-exists-p package-build-archive-dir) (package-build--message "Creating directory %s" package-build-archive-dir) (make-directory package-build-archive-dir)) (let* ((start-time (current-time)) (rcp (package-recipe-lookup name)) (url (package-recipe--upstream-url rcp)) (repo (oref rcp repo)) (fetcher (package-recipe--fetcher rcp))) (cond ((not noninteractive) (message " • Building package %s (from %s)..." name (if repo (format "%s:%s" fetcher repo) url))) (package-build-verbose (message "Package: %s" name) (message "Fetcher: %s" fetcher) (message "Source: %s\n" url))) (funcall package-build-fetch-function rcp) (package-build--select-version rcp) (package-build--package rcp) (when dump-archive-contents (package-build-dump-archive-contents)) (message "Built %s in %.3fs, finished at %s" name (float-time (time-since start-time)) (format-time-string "%FT%T%z" nil t)))) ;;;###autoload (defun package-build--package (rcp) "Build the package version specified by RCP. Return the archive entry for the package and store the package in `package-build-archive-dir'." (let ((default-directory (package-build--working-tree rcp))) (unwind-protect (progn (funcall package-build-checkout-function rcp) (let ((files (package-build-expand-files-spec rcp t))) (cond ((= (length files) 0) (error "Unable to find files matching recipe patterns")) (package-build-build-function (funcall package-build-build-function)) ((= (length files) 1) (package-build--build-single-file-package rcp files)) (t (package-build--build-multi-file-package rcp files))) (when package-build-write-melpa-badge-images (package-build--write-melpa-badge-image (oref rcp name) (oref rcp version) package-build-archive-dir)))) (funcall package-build-cleanup-function rcp)))) (defun package-build--build-single-file-package (rcp files) (let* ((name (oref rcp name)) (version (oref rcp version)) (commit (oref rcp commit)) (file (caar files)) (source (expand-file-name file)) (target (expand-file-name (concat name "-" version ".el") package-build-archive-dir)) (desc (package-build--desc-from-library rcp files))) (unless (member (downcase (file-name-nondirectory file)) (list (downcase (concat name ".el")) (downcase (concat name ".el.in")))) (error "Single file %s does not match package name %s" file name)) (copy-file source target t) (let ((enable-local-variables nil) (make-backup-files nil) (before-save-hook nil)) (with-current-buffer (find-file target) (package-build--update-or-insert-header "Package-Commit" commit) (package-build--update-or-insert-header "Package-Version" version) (package-build--ensure-ends-here-line source) (write-file target nil) (kill-buffer))) (package-build--write-pkg-readme rcp files) (package-build--write-archive-entry desc))) (defun package-build--build-multi-file-package (rcp files) (let* ((name (oref rcp name)) (version (oref rcp version)) (tmp-dir (file-name-as-directory (make-temp-file name t)))) (unwind-protect (let* ((target (expand-file-name (concat name "-" version) tmp-dir)) (desc (or (package-build--desc-from-package rcp files) (package-build--desc-from-library rcp files 'tar) (error "%s[-pkg].el matching package name is missing" name)))) (package-build--copy-package-files files target) (package-build--write-pkg-file desc target) (package-build--generate-info-files files target) (package-build--create-tar rcp tmp-dir) (package-build--write-pkg-readme rcp files) (package-build--write-archive-entry desc)) (delete-directory tmp-dir t nil)))) (defun package-build--cleanup (rcp) (cond ((cl-typep rcp 'package-git-recipe) (package-build--run-process "git" "clean" "-f" "-d" "-x")) ((cl-typep rcp 'package-hg-recipe) (package-build--run-process "hg" "purge")))) ;;;###autoload (defun package-build-all () "Build a package for each of the available recipes. If `package-build-predicate-function' is non-nil, then only packages for which that returns non-nil are build." (interactive) (let* ((start (current-time)) (recipes (package-recipe-recipes)) (total (length recipes)) (success 0) skipped invalid failed) (dolist (name recipes) (let ((rcp (with-demoted-errors "Recipe error: %S" (package-recipe-lookup name)))) (cond ((not rcp) (push name invalid)) ((and package-build-predicate-function (not (funcall package-build-predicate-function rcp))) (push name skipped)) ((with-demoted-errors "Build error: %S" (package-build-archive name) t) (cl-incf success)) ((push name failed))))) (let ((duration (/ (float-time (time-subtract (current-time) start)) 60))) (if (not (or skipped invalid failed)) (message "Successfully built all %s packages (%.0fm)" total duration) (message "Successfully built %i of %s packages (%.0fm)" success total duration) (when skipped (message "Skipped %i packages:\n%s" (length skipped) (mapconcat (lambda (n) (concat " " n)) (nreverse skipped) "\n"))) (when invalid (message "Did not built packages for %i invalid recipes:\n%s" (length invalid) (mapconcat (lambda (n) (concat " " n)) (nreverse invalid) "\n"))) (when failed (message "Building %i packages failed:\n%s" (length failed) (mapconcat (lambda (n) (concat " " n)) (nreverse failed) "\n")))))) (package-build-cleanup)) (defun package-build-cleanup () "Remove previously built packages that no longer have recipes." (interactive) (package-build-dump-archive-contents)) ;;; Archive (defun package-build-archive-alist () "Return the archive contents, without updating it first." (let ((file (expand-file-name "archive-contents" package-build-archive-dir))) (and (file-exists-p file) (with-temp-buffer (insert-file-contents file) (cdr (read (current-buffer))))))) (defun package-build-dump-archive-contents (&optional file pretty-print) "Update and return the archive contents. If non-nil, then store the archive contents in FILE instead of in the \"archive-contents\" file inside `package-build-archive-dir'. If PRETTY-PRINT is non-nil, then pretty-print instead of using one line per entry." (let (entries) (dolist (file (sort (directory-files package-build-archive-dir t ".*\.entry$") ;; Sort more recently-build packages first (lambda (f1 f2) (let ((default-directory package-build-archive-dir)) (file-newer-than-file-p f1 f2))))) (let* ((entry (with-temp-buffer (insert-file-contents file) (read (current-buffer)))) (name (car entry)) (newer-entry (assq name entries))) (if (not (file-exists-p (expand-file-name (symbol-name name) package-build-recipes-dir))) (package-build--remove-archive-files entry) ;; Prefer the more-recently-built package, which may not ;; necessarily have the highest version number, e.g. if ;; commit histories were changed. (if newer-entry (package-build--remove-archive-files entry) (push entry entries))))) (setq entries (sort entries (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))) (with-temp-file (or file (expand-file-name "archive-contents" package-build-archive-dir)) (let ((print-level nil) (print-length nil)) (if pretty-print (pp (cons 1 entries) (current-buffer)) (insert "(1") (dolist (entry entries) (newline) (insert " ") (prin1 entry (current-buffer))) (insert ")")))) entries)) (defun package-build--remove-archive-files (archive-entry) "Remove the entry and archive file for ARCHIVE-ENTRY." (package-build--message "Removing archive: %s-%s" (car archive-entry) (package-version-join (aref (cdr archive-entry) 0))) (let ((file (package-build--artifact-file archive-entry))) (when (file-exists-p file) (delete-file file))) (let ((file (package-build--archive-entry-file archive-entry))) (when (file-exists-p file) (delete-file file)))) (defun package-build--artifact-file (archive-entry) "Return the path of the file in which the package for ARCHIVE-ENTRY is stored." (pcase-let* ((`(,name . ,desc) archive-entry) (version (package-version-join (aref desc 0))) (flavour (aref desc 3))) (expand-file-name (format "%s-%s.%s" name version (if (eq flavour 'single) "el" "tar")) package-build-archive-dir))) (defun package-build--archive-entry-file (archive-entry) "Return the path of the file in which the package for ARCHIVE-ENTRY is stored." (pcase-let* ((`(,name . ,desc) archive-entry) (version (package-version-join (aref desc 0)))) (expand-file-name (format "%s-%s.entry" name version) package-build-archive-dir))) ;;; Json Exports (defun package-build-recipe-alist-as-json (file) "Dump the recipe list to FILE as json." (interactive "FDump json to file: ") (with-temp-file file (insert (json-encode (cl-mapcan (lambda (name) (ignore-errors ; Silently ignore corrupted recipes. (and (package-recipe-lookup name) (with-temp-buffer (insert-file-contents (expand-file-name name package-build-recipes-dir)) (let ((exp (read (current-buffer)))) (when (plist-member (cdr exp) :files) (plist-put (cdr exp) :files (format "%S" (plist-get (cdr exp) :files)))) (list exp)))))) (package-recipe-recipes)))))) (defun package-build--pkg-info-for-json (info) "Convert INFO so that it can be serialize to JSON in the desired shape." (pcase-let ((`(,ver ,deps ,desc ,type . (,props)) (append info nil))) (list :ver ver :deps (cl-mapcan (lambda (dep) (list (intern (format ":%s" (car dep))) (cadr dep))) deps) :desc desc :type type :props props))) (defun package-build--archive-alist-for-json () "Return the archive alist in a form suitable for JSON encoding." (cl-flet ((format-person (person) (let ((name (car person)) (mail (cdr person))) (if (and name mail) (format "%s <%s>" name mail) (or name (format "<%s>" mail)))))) (cl-mapcan (lambda (entry) (list (intern (format ":%s" (car entry))) (let* ((info (cdr entry)) (extra (aref info 4)) (maintainer (assq :maintainer extra)) (authors (assq :authors extra))) (when maintainer (setcdr maintainer (format-person (cdr maintainer)))) (when authors (if (cl-every #'listp (cdr authors)) (setcdr authors (mapcar #'format-person (cdr authors))) (assq-delete-all :authors extra))) (package-build--pkg-info-for-json info)))) (package-build-archive-alist)))) (defun package-build-archive-alist-as-json (file) "Dump the build packages list to FILE as json." (with-temp-file file (insert (json-encode (package-build--archive-alist-for-json))))) ;;; _ (provide 'package-build) ;;; package-build.el ends here ================================================ FILE: lisp/extern/package-build/25/package-recipe-mode.el ================================================ ;;; package-recipe-mode.el --- Minor mode for editing package recipes -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2011-2023 Donald Ephraim Curtis ;; Copyright (C) 2012-2023 Steve Purcell ;; Copyright (C) 2016-2023 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; This library defines the minor mode `package-build-minor-mode', ;; which will likely be replaced with the `emacs-lisp-mode' derived ;; `package-recipe-mode' eventually. ;;; Code: (require 'package-build) (defvar package-build-minor-mode-map (let ((m (make-sparse-keymap))) (define-key m (kbd "C-c C-c") 'package-build-current-recipe) m) "Keymap for `package-build-minor-mode'.") (define-minor-mode package-build-minor-mode "Helpful functionality for building packages." :lighter " PBuild" (when package-build-minor-mode (message "Use C-c C-c to build this recipe."))) ;;;###autoload (defun package-build-create-recipe (name fetcher) "Create a new recipe for the package named NAME using FETCHER." (interactive (list (read-string "Package name: ") (intern (completing-read "Fetcher: " package-recipe--fetchers nil t nil nil "github")))) (let ((recipe-file (expand-file-name name package-build-recipes-dir))) (when (file-exists-p recipe-file) (error "Recipe already exists")) (find-file recipe-file) (insert (pp-to-string `(,(intern name) :fetcher ,fetcher ,@(cl-case fetcher (github (list :repo "USER/REPO")) (t (list :url "SCM_URL_HERE")))))) (emacs-lisp-mode) (package-build-minor-mode) (goto-char (point-min)))) ;;;###autoload (defun package-build-current-recipe () "Build archive for the recipe defined in the current buffer." (interactive) (unless (and (buffer-file-name) (file-equal-p (file-name-directory (buffer-file-name)) package-build-recipes-dir)) (error "Buffer is not visiting a recipe")) (when (buffer-modified-p) (if (y-or-n-p (format "Save file %s? " buffer-file-name)) (save-buffer) (error "Aborting"))) (check-parens) (let ((name (file-name-nondirectory (buffer-file-name)))) (package-build-archive name t) (let ((output-buffer-name "*package-build-result*")) (with-output-to-temp-buffer output-buffer-name (princ ";; Please check the following package descriptor.\n") (princ ";; If the correct package description or dependencies are missing,\n") (princ ";; then the source .el file is likely malformed, and should be fixed.\n") (pp (assoc (intern name) (package-build-archive-alist)))) (with-current-buffer output-buffer-name (emacs-lisp-mode) (view-mode))) (when (yes-or-no-p "Install new package? ") (package-install-file (package-build--artifact-file (assq (intern name) (package-build-archive-alist))))))) (provide 'package-recipe-mode) ;;; package-recipe-mode.el ends here ================================================ FILE: lisp/extern/package-build/25/package-recipe.el ================================================ ;;; package-recipe.el --- Package recipes as EIEIO objects -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2018-2023 Jonas Bernoulli ;; Author: Jonas Bernoulli ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; Package recipes as EIEIO objects. ;;; Code: (require 'eieio) (require 'url-parse) (defvar package-build-use-git-remote-hg) (defvar package-build-recipes-dir) (defvar package-build-working-dir) ;;; Classes (defclass package-recipe () ((url-format :allocation :class :initform nil) (repopage-format :allocation :class :initform nil) (stable-p :allocation :class :initform nil) (name :initarg :name :initform nil) (url :initarg :url :initform nil) (repo :initarg :repo :initform nil) (repopage :initarg :repopage :initform nil) (files :initarg :files :initform nil) (branch :initarg :branch :initform nil) (commit :initarg :commit :initform nil) (time :initform nil) (version :initform nil) (version-regexp :initarg :version-regexp :initform nil) (old-names :initarg :old-names :initform nil)) :abstract t) ;;;; Git (defclass package-git-recipe (package-recipe) ()) (defclass package-github-recipe (package-git-recipe) ((url-format :initform "https://github.com/%s.git") (repopage-format :initform "https://github.com/%s"))) (defclass package-gitlab-recipe (package-git-recipe) ((url-format :initform "https://gitlab.com/%s.git") (repopage-format :initform "https://gitlab.com/%s"))) (defclass package-codeberg-recipe (package-git-recipe) ((url-format :initform "https://codeberg.org/%s.git") (repopage-format :initform "https://codeberg.org/%s"))) (defclass package-sourcehut-recipe (package-git-recipe) ((url-format :initform "https://git.sr.ht/~%s") (repopage-format :initform "https://git.sr.ht/~%s"))) ;;;; Mercurial (defclass package-hg-recipe (package-recipe) ()) (defclass package-git-remote-hg-recipe (package-git-recipe) ()) ;;; Methods (cl-defmethod package-recipe--working-tree ((rcp package-recipe)) (file-name-as-directory (expand-file-name (oref rcp name) package-build-working-dir))) (cl-defmethod package-recipe--upstream-url ((rcp package-recipe)) (or (oref rcp url) (format (oref rcp url-format) (oref rcp repo)))) (cl-defmethod package-recipe--upstream-url ((rcp package-git-remote-hg-recipe)) (concat "hg::" (oref rcp url))) (cl-defmethod package-recipe--upstream-protocol ((rcp package-recipe)) (let ((url (package-recipe--upstream-url rcp))) (cond ((string-match "\\`\\([a-z]+\\)://" url) (match-string 1 url)) ((string-match "\\`[^:/ ]+:" url) "ssh") (t "file")))) (cl-defmethod package-recipe--fetcher ((rcp package-recipe)) (substring (symbol-name (eieio-object-class rcp)) 8 -7)) ;;; Constants (defconst package-recipe--forge-fetchers '(github gitlab codeberg sourcehut)) (defconst package-recipe--fetchers (append '(git hg) package-recipe--forge-fetchers)) ;;; Interface (defun package-recipe-recipes () "Return a list of the names of packages with available recipes." (directory-files package-build-recipes-dir nil "^[^.]")) (defun package-recipe-read-name () "Read the name of a package for which a recipe is available." (completing-read "Package: " (package-recipe-recipes))) (defun package-recipe-lookup (name) "Return a recipe object for the package named NAME. If no such recipe file exists or if the contents of the recipe file is invalid, then raise an error." (let ((file (expand-file-name name package-build-recipes-dir))) (if (file-exists-p file) (let* ((recipe (with-temp-buffer (insert-file-contents file) (read (current-buffer)))) (plist (cdr recipe)) (fetcher (plist-get plist :fetcher)) key val args) (package-recipe--validate recipe name) (while (and (setq key (pop plist)) (setq val (pop plist))) (unless (eq key :fetcher) (push val args) (push key args))) (when (and package-build-use-git-remote-hg (eq fetcher 'hg)) (setq fetcher 'git-remote-hg)) (apply (intern (format "package-%s-recipe" fetcher)) name :name name args)) (error "No such recipe: %s" name)))) ;;; Validation (defun package-recipe-validate-all () "Validate all recipes." (interactive) (dolist (name (package-recipe-recipes)) (condition-case err (package-recipe-lookup name) (error (message "Invalid recipe for %s: %S" name (cdr err)))))) (defun package-recipe--validate (recipe name) "Perform some basic checks on the raw RECIPE for the package named NAME." (pcase-let ((`(,ident . ,plist) recipe)) (cl-assert ident) (cl-assert (symbolp ident)) (cl-assert (string= (symbol-name ident) name) nil "Recipe '%s' contains mismatched package name '%s'" name ident) (cl-assert plist) (let* ((symbol-keys '(:fetcher)) (string-keys '(:url :repo :commit :branch :version-regexp)) (list-keys '(:files :old-names)) (all-keys (append symbol-keys string-keys list-keys))) (dolist (thing plist) (when (keywordp thing) (cl-assert (memq thing all-keys) nil "Unknown keyword %S" thing))) (let ((fetcher (plist-get plist :fetcher))) (cl-assert fetcher nil ":fetcher is missing") (if (memq fetcher package-recipe--forge-fetchers) (progn (cl-assert (plist-get plist :repo) ":repo is missing") (cl-assert (not (plist-get plist :url)) ":url is redundant")) (cl-assert (plist-get plist :url) ":url is missing"))) (dolist (key symbol-keys) (let ((val (plist-get plist key))) (when val (cl-assert (symbolp val) nil "%s must be a symbol but is %S" key val)))) (dolist (key list-keys) (let ((val (plist-get plist key))) (when val (cl-assert (listp val) nil "%s must be a list but is %S" key val)))) (dolist (key string-keys) (let ((val (plist-get plist key))) (when val (cl-assert (stringp val) nil "%s must be a string but is %S" key val)))) (when-let* ((spec (plist-get plist :files))) ;; `:defaults' is only allowed as the first element. ;; If we find it in that position, skip over it. (when (eq (car spec) :defaults) (setq spec (cdr spec))) ;; All other elements have to be strings or lists of strings. ;; A list whose first element is `:exclude' is also valid. (dolist (entry spec) (unless (or (and (stringp entry) (not (equal entry "*"))) (and (listp entry) (or (eq (car entry) :exclude) (stringp (car entry))) (seq-every-p (lambda (e) (and (stringp e) (not (equal e "*")))) (cdr entry)))) (error "Invalid files spec entry %S" entry)))) ;; Silence byte compiler of Emacs 28. It appears that uses ;; inside cl-assert sometimes, but not always, do not count. (list name ident all-keys)) recipe)) (provide 'package-recipe) ;;; package-recipe.el ends here ================================================ FILE: lisp/extern/package-build/26/package-build-badges.el ================================================ ;;; package-build-badges.el --- Create badges for packages -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2011-2023 Donald Ephraim Curtis ;; Copyright (C) 2012-2023 Steve Purcell ;; Copyright (C) 2018-2023 Jonas Bernoulli ;; Copyright (C) 2021-2023 Free Software Foundation, Inc ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; Create badges for packages. ;; The code in this file was lifted from `elpa-admin'. ;;; Code: (defvar package-build-badge-data) (defun package-build--write-badge-image ( name version target-dir &optional archive color) "Make badge svg file. This is essentially a copy of `elpaa--make-badge'." (let* ((file (expand-file-name (concat name "-badge.svg") target-dir)) (left (or archive (car package-build-badge-data) "myElpa")) (right (url-hexify-string version)) (color (or color (cadr package-build-badge-data) "#ff491b")) (lw (package-build-badge--string-width left)) (rw (package-build-badge--string-width right)) (pad (package-build-badge--string-width "x")) (width (/ (+ lw rw (* 4 pad)) 10)) (offset -10) ;; Small alignment correction (ctx `((offset . ,offset) (left . ,left) (right . ,right) (lw . ,lw) (rw . ,rw) (width . ,width) (color . ,color) (pad . ,pad)))) (with-temp-buffer (insert (replace-regexp-in-string "{\\([^}]+\\)}" (lambda (str) (url-insert-entities-in-string (format "%s" (eval (read (match-string 1 str)) ctx)))) (eval-when-compile (replace-regexp-in-string "[ \t\n]+" " " (replace-regexp-in-string "'" "\"" " {left}: {right} {left} {right} "))))) (write-region (point-min) (point-max) file)))) (defun package-build-badge--string-width (str) "Determine string width in pixels of STR." (with-temp-buffer ;; ImageMagick 7.1.0 or later requires using the "magick" driver, ;; rather than "convert" directly, but Debian doesn't provide it ;; yet (2021). (let ((args `(,@(if (executable-find "magick") '("magick" "convert") '("convert")) "-debug" "annotate" "xc:" "-font" "DejaVu-Sans" "-pointsize" "110" "-annotate" "0" ,str "null:"))) (apply #'call-process (car args) nil t nil (delq nil (cdr args))) (goto-char (point-min)) (if (not (re-search-forward "Metrics:.*?width: \\([0-9]+\\)")) (error "Could not determine string width") (let ((width (string-to-number (match-string 1)))) ;; This test aims to catch the case where the font is missing, ;; but it seems it only works in some cases :-( (if (and (> (string-width str) 0) (not (> width 0))) (progn (message "convert:\n%s" (buffer-string)) (error "Could not determine string width")) width)))))) (provide 'package-build-badges) ;;; package-badges.el ends here ================================================ FILE: lisp/extern/package-build/26/package-build.el ================================================ ;;; package-build.el --- Tools for assembling a package archive -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2011-2023 Donald Ephraim Curtis ;; Copyright (C) 2012-2023 Steve Purcell ;; Copyright (C) 2016-2023 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Steve Purcell ;; Jonas Bernoulli ;; Phil Hagelberg ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; Package-Version: 4.0.0.50-git ;; Package-Requires: ((emacs "26.1")) ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; This file allows a curator to publish an archive of Emacs packages. ;; The archive is generated from a set of recipes, which describe elisp ;; projects and repositories from which to get them. The term "package" ;; here is used to mean a specific version of a project that is prepared ;; for download and installation. ;;; Code: (require 'cl-lib) (require 'pcase) (require 'subr-x) (require 'package) (require 'lisp-mnt) (require 'json) (require 'package-recipe) (require 'package-build-badges) ;;; Options (defvar package-build--melpa-base (file-name-directory (directory-file-name (file-name-directory (or load-file-name (buffer-file-name)))))) (defgroup package-build nil "Tools for building package.el-compliant packages from upstream source code." :group 'development) (defcustom package-build-working-dir (expand-file-name "working/" package-build--melpa-base) "Directory in which to keep checkouts." :group 'package-build :type 'string) (defcustom package-build-archive-dir (expand-file-name "packages/" package-build--melpa-base) "Directory in which to keep compiled archives." :group 'package-build :type 'string) (defcustom package-build-recipes-dir (expand-file-name "recipes/" package-build--melpa-base) "Directory containing recipe files." :group 'package-build :type 'string) (defcustom package-build-verbose t "When non-nil, then print additional progress information." :group 'package-build :type 'boolean) (defcustom package-build-stable nil "Whether to build release or snapshot packages. If nil, snapshot packages are build, otherwise release packages are build. `package-build-snapshot-version-functions' and/or `package-build-release-version-functions' are used to determine the appropriate version for each package and how the version string is formatted." :group 'package-build :type 'boolean) (make-obsolete-variable 'package-build-get-version-function 'package-build-stable "Package-Build 5.0.0") (defvar package-build-get-version-function nil "This variable is obsolete and its value should be nil. If this is non-nil, then it overrides `package-build-release-version-functions' and `package-build-snapshot-version-functions'.") (defcustom package-build-release-version-functions (list #'package-build-tag-version) "Functions used to determine the current release of a package. Each function is called in order, with the recipe object as argument, until one returns non-nil. The returned value must have the form (COMMIT TIME VERSION), where COMMIT is the commit chosen by the function, TIME is its committer date, and VERSION is the version string chosen for COMMIT. If obsolete `package-build-get-version-function' is non-nil, then that overrides the value set here." :group 'package-build :type 'hook :options (list #'package-build-tag-version #'package-build-header-version #'package-build-pkg-version)) (defcustom package-build-snapshot-version-functions (list #'package-build-timestamp-version) "Function used to determine the current snapshot of a package. Each function is called in order, with the recipe object as argument, until one returns non-nil. The returned value must have the form (COMMIT TIME VERSION), where COMMIT is the commit chosen by the function, TIME is its committer date, and VERSION is the version string chosen for COMMIT. Some of the functions that return snapshot versions, internally use `package-build-release-version-functions' to determine the current release, which they use as part of the returned VERSION. If obsolete `package-build-get-version-function' is non-nil, then that overrides the value set here." :group 'package-build :type 'hook :options (list #'package-build-release+count-version #'package-build-release+timestamp-version #'package-build-timestamp-version)) (defcustom package-build-predicate-function nil "Predicate used by `package-build-all' to determine which packages to build. If non-nil, this function is called with the recipe object as argument, and must return non-nil if the package is to be build. If nil (the default), then all packages are build." :group 'package-build :type '(choice (const :tag "build all") function)) (defcustom package-build-build-function #'package-build--build-multi-file-package "Low-level function used to build a package. By default a tarball is used for all packages, including those consisting of a single file. It this is nil, then single-file packages are distributed without using tarballs." :group 'package-build :type '(choice (const :tag "use tarball for all packages" package-build--build-multi-file-package) (const :tag "only use tarball for multi-file packages" nil) function)) ;; NOTE that these hooks are still experimental. Let me know if these ;; are potentially useful for you and whether any changes are required ;; to make them more appropriate for your usecase. (defvar package-build-worktree-function #'package-recipe--working-tree) (defvar package-build-early-worktree-function #'package-recipe--working-tree) (defvar package-build-fetch-function #'package-build--fetch) (defvar package-build-checkout-function #'package-build--checkout) (defvar package-build-cleanup-function #'package-build--cleanup) (defcustom package-build-timeout-executable "timeout" "Path to a GNU coreutils \"timeout\" command if available. This must be a version which supports the \"-k\" option. On MacOS it is possible to install coreutils using Homebrew or similar, which will provide the GNU timeout program as \"gtimeout\"." :group 'package-build :type '(file :must-match t)) (defcustom package-build-timeout-secs nil "Wait this many seconds for external processes to complete. If an external process takes longer than specified here to complete, then it is terminated. If nil, then no time limit is applied. This setting requires `package-build-timeout-executable' to be set." :group 'package-build :type 'number) (defcustom package-build-tar-executable "tar" "Path to a (preferably GNU) tar command. Certain package names (e.g. \"@\") may not work properly with a BSD tar. On MacOS it is possible to install coreutils using Homebrew or similar, which will provide the GNU timeout program as \"gtar\"." :group 'package-build :type '(file :must-match t)) (defvar package-build--tar-type nil "Type of `package-build-tar-executable'. Can be `gnu' or `bsd'; nil means the type is not decided yet.") (define-obsolete-variable-alias 'package-build-write-melpa-badge-images 'package-build-badge-data "Package-Build 5.0.0") (defcustom package-build-badge-data nil "Text and color used in badge images, if any. If nil (the default), then no badge images are generated, otherwise this has the form (NAME COLOR). MELPA sets the value in its top-level Makefile, to different values, depending on the channel that is being build." :group 'package-build :type '(list (string :tag "Archive name") color)) (defcustom package-build-version-regexp "\\`[rRvV]?\\(?1:[0-9]+\\(\\.[0-9]+\\)*\\)\\'" "Regexp used to match valid version-strings. The first capture is used to extract the actual version string. Strings matched by that group must be valid according to `version-to-list', but the used regexp can be more strict. The default value supports only releases but no pre-releases. It also intentionally ignores cedrtain unfortunate version strings such as \"1A\" or \".5\", and only supports \".\" as separator. The part before the first capture group should match prefixes commonly used in version tags. Note that this variable can be overridden in a package's recipe, using the `:version-regexp' slot." :group 'package-build :type 'string) (defcustom package-build-allowed-git-protocols '("https" "file" "ssh") "Protocols that can be used to fetch from upstream with git. By default insecure protocols, such as \"http\" or \"git\", are disallowed." :group 'package-build :type '(repeat string)) (defvar package-build-use-git-remote-hg nil "Whether to use `git-remote-hg' remote helper for mercurial repos.") (defvar package-build--inhibit-fetch nil "Whether to inhibit fetching. Useful for testing purposes.") (defvar package-build--inhibit-checkout nil "Whether to inhibit checkout. Useful for testing purposes.") (defvar package-build--inhibit-build nil "Whether to inhibit building. Useful for testing purposes.") ;;; Generic Utilities (defun package-build--message (format-string &rest args) "Behave like `message' if `package-build-verbose' is non-nil. Otherwise do nothing. FORMAT-STRING and ARGS are as per that function." (when package-build-verbose (apply #'message format-string args))) ;;; Version Handling ;;;; Common (defun package-build--select-version (rcp) (pcase-let* ((default-directory (package-build--working-tree rcp t)) (`(,commit ,time ,version) (cond ((with-no-warnings package-build-get-version-function) (display-warning 'package-build "\ Variable `package-build-get-version-function' is obsolete. Instead set `package-build-release-version-functions' and/or `package-build-snapshot-version-functions', and set `package-build-stable' to control whether releases or snapshots are build.") (with-no-warnings (funcall package-build-get-version-function rcp))) (package-build-stable (run-hook-with-args-until-success 'package-build-release-version-functions rcp)) ((run-hook-with-args-until-success 'package-build-snapshot-version-functions rcp))))) (unless version (error "Cannot detect version for %s" (oref rcp name))) (oset rcp commit commit) (oset rcp time time) (oset rcp version version))) (cl-defmethod package-build--select-commit ((rcp package-git-recipe) rev exact) (pcase-let* ((`(,hash ,time) (split-string (car (apply #'process-lines "git" "log" "-n1" "--first-parent" "--no-show-signature" "--pretty=format:%H %cd" "--date=unix" rev (and (not exact) (cons "--" (package-build--spec-globs rcp))))) " "))) (list hash (string-to-number time)))) (cl-defmethod package-build--select-commit ((rcp package-hg-recipe) rev exact) (pcase-let* ((`(,hash ,time ,_timezone) (split-string (car (apply #'process-lines ;; The "date" keyword uses UTC. The "hgdate" filter ;; returns two integers separated by a space; the ;; unix timestamp and the timezone offset. We use ;; "hgdate" because that makes it easier to discard ;; the time zone offset, which doesn't interest us. "hg" "log" "--limit" "1" "--template" "{node} {date|hgdate}\n" "--rev" rev (and (not exact) (cons "--" (package-build--spec-globs rcp))))) " "))) (list hash (string-to-number time)))) ;;;; Tag (defun package-build-tag-version (rcp) "Determine version corresponding to largest version tag for RCP. Return (COMMIT-HASH COMMITTER-DATE VERSION-STRING)." (let ((regexp (or (oref rcp version-regexp) package-build-version-regexp)) (tag nil) (version '(0))) (dolist (n (package-build--list-tags rcp)) (let ((v (ignore-errors (version-to-list (and (string-match regexp n) (match-string 1 n)))))) (when (and v (version-list-<= version v)) (if (cl-typep rcp 'package-git-recipe) (setq tag (concat "refs/tags/" n)) (setq tag n)) (setq version v)))) (and tag (pcase-let ((`(,hash ,time) (package-build--select-commit rcp tag t))) (list hash time (package-version-join version)))))) (cl-defmethod package-build--list-tags ((_rcp package-git-recipe)) (process-lines "git" "tag" "--list")) (cl-defmethod package-build--list-tags ((_rcp package-hg-recipe)) (process-lines "hg" "tags" "--quiet")) (define-obsolete-function-alias 'package-build-get-tag-version 'package-build-tag-version "Package-Build 5.0.0") ;;;; Header (defun package-build-header-version (rcp) "Return version specified in the header of the main library. Walk the history of the main library until a commit is found which changes the `Package-Version' or `Version' header in the main library to a version that qualifies as a release, ignoring any pre-releases. Return (COMMIT-HASH COMMITTER-DATE VERSION-STRING)." (when-let* ((lib (package-build--main-library rcp))) (with-temp-buffer (let (commit date version) (save-excursion (package-build--insert-version-header-log rcp (file-relative-name lib))) (while (and (not version) (re-search-forward "^commit \\([^ ]+\\) \\(.+\\)" nil t)) (setq commit (match-string 1)) (setq date (match-string 2)) (let ((end (save-excursion (re-search-forward "^$" nil t)))) (when (re-search-forward "^\\+;;* *\\(Package-\\)?Version: *\\(.+\\)" end t) (let ((ver (match-string 2))) (when (and (not (equal ver "0")) (string-match "\\`\\([0-9]+\\)\\(\\.[0-9]+\\)*\\'" ver)) (setq version ver)))) (when end (goto-char end)))) (when version (list commit (string-to-number date) (package-version-join (version-to-list version)))))))) (defun package-build--main-library (rcp) (package-build--match-library rcp)) (defun package-build--match-library (rcp &optional filename) (let ((libs (package-build--list-libraries rcp)) (filename (or filename (concat (oref rcp name) ".el")))) (cond ((car (member (concat "lisp/" filename) libs))) ((car (member filename libs))) ((cl-find filename libs :test #'equal :key #'file-name-nondirectory))))) (cl-defmethod package-build--list-libraries ((_rcp package-git-recipe)) (process-lines "git" "ls-files" "*.el")) (cl-defmethod package-build--list-libraries ((_rcp package-hg-recipe)) (process-lines "hg" "files" "--include" "**/*.el")) (cl-defmethod package-build--insert-version-header-log ((_rcp package-git-recipe) lib) (call-process "git" nil t nil "log" "--first-parent" "--pretty=format:commit %H %cd" "--date=unix" "-L" (format "/^;;* *\\(Package-\\)\\?Version:/,+1:%s" lib))) (cl-defmethod package-build--insert-version-header-log ((_rcp package-hg-recipe) _lib) (call-process "hg" nil t nil "log" "--first-parent" "--template" "commit: {node} {date|hgdate}\n" )) ; TODO What is the equivalent of Git's "-L"? ;;;; NAME-pkg (defun package-build-pkg-version (rcp) "Return version specified in the \"NAME-pkg.el\" file. Return (COMMIT-HASH COMMITTER-DATE VERSION-STRING)." (when-let* ((file (package-build--pkgfile rcp))) (let ((regexp (or (oref rcp version-regexp) package-build-version-regexp)) commit date version) (catch 'before-latest (pcase-dolist (`(,c ,d) (package-build--pkgfile-commits rcp file)) (with-temp-buffer (save-excursion (package-build--insert-pkgfile rcp c file)) (when-let* ((n (ignore-errors (nth 2 (read (current-buffer))))) (v (ignore-errors (version-to-list (and (string-match regexp n) ;; Use match-group 0, not 1, because in ;; this file a version string without a ;; prefix is expected. (match-string 0 n)))))) (when (and version (not (equal v version))) (throw 'before-latest nil)) (setq commit c) (setq date d) (setq version v))))) (and version (list commit (string-to-number date) (package-version-join version)))))) (defun package-build--pkgfile (rcp) (package-build--match-library rcp (concat (oref rcp name) "-pkg.el"))) (cl-defmethod package-build--pkgfile-commits ((_rcp package-git-recipe) file) (mapcar (lambda (line) (split-string line " ")) (process-lines "git" "log" "--first-parent" "--pretty=format:%H %cd" "--date=unix" "--" file))) (cl-defmethod package-build--pkgfile-commits ((_rcp package-hg-recipe) file) (mapcar (lambda (line) (seq-take (split-string line " ") 2)) (process-lines "hg" "log" "--template" "{node} {date|hgdate}\n" "--" file))) (cl-defmethod package-build--insert-pkgfile ((_rcp package-git-recipe) commit file) (call-process "git" nil t nil "show" (concat commit ":" file))) (cl-defmethod package-build--insert-pkgfile ((_rcp package-hg-recipe) commit file) (call-process "hg" nil t nil "cat" "-r" commit file)) ;;;; Timestamp (defun package-build-timestamp-version (rcp) "Determine timestamp version corresponding to latest relevant commit for RCP. Return (COMMIT-HASH COMMITTER-DATE VERSION-STRING), where VERSION-STRING has the format \"%Y%m%d.%H%M\"." (pcase-let ((`(,hash ,time) (package-build--timestamp-version rcp))) (list hash time ;; We remove zero-padding of the HH portion, as ;; that is lost when stored in archive-contents. (concat (format-time-string "%Y%m%d." time t) (format "%d" (string-to-number (format-time-string "%H%M" time t))))))) (cl-defmethod package-build--timestamp-version ((rcp package-git-recipe)) (pcase-let* ((commit (oref rcp commit)) (branch (oref rcp branch)) (branch (and branch (concat "origin/" branch))) (rev (or commit branch "origin/HEAD")) (`(,rev-hash ,rev-time) (package-build--select-commit rcp rev commit)) (`(,tag-hash ,tag-time) (package-build-tag-version rcp))) ;; If the latest commit that touches a relevant file is an ancestor of ;; the latest tagged release and the tag is reachable from origin/HEAD ;; (i.e., it isn't on a separate release branch) then use the tagged ;; release. Snapshots should not be older than the latest release. (if (and tag-hash (zerop (call-process "git" nil nil nil "merge-base" "--is-ancestor" rev-hash tag-hash)) (zerop (call-process "git" nil nil nil "merge-base" "--is-ancestor" tag-hash rev))) (list tag-hash tag-time) (list rev-hash rev-time)))) (cl-defmethod package-build--timestamp-version ((rcp package-hg-recipe)) (let* ((commit (oref rcp commit)) (branch (or (oref rcp branch) "default")) (rev (format "sort(ancestors(%s), -rev)" (or commit (format "max(branch(%s))" branch))))) (package-build--select-commit rcp rev nil))) (define-obsolete-function-alias 'package-build-get-snapshot-version 'package-build-snapshot-version "Package-Build 5.0.0") ;;;; Release+Timestamp (defun package-build-release+timestamp-version (rcp) "Determine version string in the \"RELEASE.0.TIMESTAMP\" format for RCP. *Experimental* This function is still subject to change. Use `package-build-release-version-functions' to determine RELEASE. TIMESTAMP is the COMMITTER-DATE for the identified last relevant commit, using the format \"%Y%m%d.%H%M\". Return (COMMIT-HASH COMMITTER-DATE VERSION-STRING)." (pcase-let* ((`(,scommit ,stime ,sversion) (package-build-timestamp-version rcp)) (`(,rcommit ,rtime ,rversion) (run-hook-with-args-until-success 'package-build-release-version-functions rcp)) (ahead (package-build--commit-count rcp scommit rcommit))) (cond ((> ahead 0) (list scommit stime (package-version-join (nconc (if rversion (version-to-list rversion) (list 0 0)) (list 0) (version-to-list sversion))))) (t ;; The latest commit, which touched a relevant file, is either the ;; latest release itself, or a commit before that. Distribute the ;; same commit/release as on the stable channel; as it would not ;; make sense for the development channel to lag behind the latest ;; release. (list rcommit rtime (package-version-join rversion)))))) ;;;; Release+Count (defun package-build-release+count-version (rcp &optional single-count) "Determine version string in the \"RELEASE.0.COUNT\" format for RCP. *Experimental* This function is still subject to change. Use `package-build-release-version-functions' to determine RELEASE. COUNT is the number of commits since RELEASE until the last relevant commit. If RELEASE is the same as for the last snapshot but COUNT is not larger than for that snapshot because history was rewritten, then use \"RELEASE.0.OLDCOUNT.NEWCOUNT\". Return (COMMIT-HASH COMMITTER-DATE VERSION-STRING). \n(fn RCP)" (pcase-let* ;; Get the commit but ignore the associated timestamp. ((`(,scommit ,stime ,_) (package-build-timestamp-version rcp)) (`(,rcommit ,rtime ,version) (run-hook-with-args-until-success 'package-build-release-version-functions rcp)) (version (and rcommit (version-to-list version))) (merge-base (and rcommit (package-build--merge-base rcp scommit rcommit))) (ahead (package-build--commit-count rcp scommit rcommit))) (cond ((or (when (not rcommit) ;; No appropriate release detected. (setq version (list 0 0)) t) (when (not merge-base) ;; As a result of butchered history rewriting, version tags ;; share no history at all with what is currently reachable ;; from the tip. Completely ignore these unreachable tags and ;; behave as if no version tags existed at all. Unfortunately ;; that means that users, who have installed a snapshot based ;; on a now abandoned tag, are stuck on that snapshot until ;; upstream creates a new version tag. (setq version (list 0 0)) t) ;; Snapshot commit is newer than latest release (or there is no ;; release). (> ahead 0)) (list scommit stime (package-version-join (append version (list 0) ;; (This argument *could* be used by a wrapper.) (if single-count ahead ; Pretend time-travel doesn't happen. (package-build--ensure-count-increase rcp (copy-sequence version) ahead)))))) (t ;; The latest commit, which touched a relevant file, is either the ;; latest release itself, or a commit before that. Distribute the ;; same commit/release as on the stable channel; as it would not ;; make sense for the development channel to lag behind the latest ;; release. (list rcommit rtime (package-version-join version)))))) (defun package-build--ensure-count-increase (rcp version ahead) (if-let* ((previous (cdr (assq (intern (oref rcp name)) (package-build-archive-alist))))) ;; Because upstream may have rewritten history, we cannot be certain ;; that appending the new count of commits would result in a version ;; string that is greater than the version string used for the ;; previous snapshot. (let ((count (list ahead)) (pversion (aref previous 0)) (pcount nil)) (when (and ;; If there is no zero part, then we know that the previous ;; snapshot exactly matched a tagged release (in which case ;; we do not append zero and the count). (memq 0 pversion) ;; Likewise if there is a tag that exactly matches the ;; previous (non-)snapshot, then there is no old count ;; which we would have to compare with the new count. (not (member (mapconcat #'number-to-string pversion ".") (package-build--list-tags rcp)))) ;; The previous snapshot does not exactly match a tagged ;; version. We must split the version string into its tag ;; and count parts. The last zero part is the boundary. (let ((split (cl-position 0 pversion :from-end t)) (i 0) (tagged nil)) (while (< i split) (push (pop pversion) tagged) (cl-incf i)) (setq pcount (cdr pversion)) (setq pversion (nreverse tagged))) ;; Determine whether we can reset the count or increase it, or ;; whether we have to preserve the old count due to rewritten ;; history in order to ensure that the new snapshot version is ;; greater than the previous snapshot. ;; If the previous and current snapshot commits do not follow ;; the same tag, then their respective counts of commits since ;; their respective tag have no relation to each other and we ;; can simply reset the count, determined above. (when (equal version pversion) ;; If the new count is smaller than the old, then we keep the ;; old count and append the new count as a separate version ;; part. ;; ;; We may have had to do that for previous snapshots, possibly ;; even for multiple consecutive snapshots. Beginning at the ;; end, scrape of all counts that are smaller than the current ;; count, but leave the others intact. (setq pcount (nreverse pcount)) (while (and pcount (> ahead (car pcount))) (pop pcount)) (when pcount ;; This snapshot is based on the same tag as the previous ;; snapshot and, due to history rewriting, the count did ;; not increase. (setq count (nreverse (cons (car count) pcount)))))) count) (list ahead))) (cl-defmethod package-build--merge-base ((_rcp package-git-recipe) a b) (ignore-errors (car (process-lines "git" "merge-base" a b)))) (cl-defmethod package-build--merge-base ((_rcp package-hg-recipe) a b) (car (process-lines "hg" "log" "--template" "{node}\\n" "--rev" (format "ancestor(%s, %s)" a b)))) (cl-defmethod package-build--commit-count ((_rcp package-git-recipe) rev since) (string-to-number (car (if since (process-lines "git" "rev-list" "--count" rev (concat "^" since)) (process-lines "git" "rev-list" "--count" rev))))) (cl-defmethod package-build--commit-count ((_rcp package-hg-recipe) rev since) (length (process-lines "hg" "log" "--template" "{rev}\\n" "--rev" (if since (format "only(%s, %s)" rev since) (format "ancestors(%s)" rev))))) ;;; Run Process (defun package-build--run-process (command &rest args) "Run COMMAND with ARGS in `default-directory'. We use this to wrap commands is proper environment settings and with a timeout so that no command can block the build process." (unless (file-directory-p default-directory) (error "Cannot run process in non-existent directory: %s" default-directory)) (with-temp-buffer (pcase-let* ((`(,command . ,args) (nconc (and (not (eq system-type 'windows-nt)) (list "env" "LC_ALL=C")) (if (and package-build-timeout-secs package-build-timeout-executable) (nconc (list package-build-timeout-executable "-k" "60" (number-to-string package-build-timeout-secs) command) args) (cons command args)))) (exit-code (apply #'call-process command nil (current-buffer) nil args))) (unless (zerop exit-code) (message "\nCommand '%s' exited with non-zero exit-code: %d\n" (mapconcat #'shell-quote-argument argv " ") exit-code) (message "%s" (buffer-string)) (error "Command exited with non-zero exit-code: %d" exit-code))))) ;;; Worktree (defun package-build--working-tree (rcp &optional early) (if early (funcall package-build-early-worktree-function rcp) (funcall package-build-worktree-function rcp))) ;;; Fetch (cl-defmethod package-build--fetch ((rcp package-git-recipe)) (let ((dir (package-build--working-tree rcp t)) (url (package-recipe--upstream-url rcp)) (protocol (package-recipe--upstream-protocol rcp))) (unless (member protocol package-build-allowed-git-protocols) (error "Fetching using the %s protocol is not allowed" protocol)) (cond ((and (file-exists-p (expand-file-name ".git" dir)) (let ((default-directory dir)) (string= (car (process-lines "git" "config" "remote.origin.url")) url))) (unless package-build--inhibit-fetch (let ((default-directory dir)) (package-build--message "Updating %s" dir) (package-build--run-process "git" "fetch" "-f" "--tags" "origin") ;; We might later checkout "origin/HEAD". Sadly "git fetch" ;; cannot be told to keep it up-to-date, so we have to make ;; a second request. (package-build--run-process "git" "remote" "set-head" "origin" "--auto")))) (t (when (file-exists-p dir) (delete-directory dir t)) (package-build--message "Cloning %s to %s" url dir) (make-directory package-build-working-dir t) (let ((default-directory package-build-working-dir)) (package-build--run-process "git" "clone" url dir)))))) (cl-defmethod package-build--fetch ((rcp package-hg-recipe)) (let ((dir (package-build--working-tree rcp t)) (url (package-recipe--upstream-url rcp))) (cond ((and (file-exists-p (expand-file-name ".hg" dir)) (let ((default-directory dir)) (string= (car (process-lines "hg" "paths" "default")) url))) (unless package-build--inhibit-fetch (let ((default-directory dir)) (package-build--message "Updating %s" dir) (package-build--run-process "hg" "pull") (package-build--run-process "hg" "update")))) (t (when (file-exists-p dir) (delete-directory dir t)) (package-build--message "Cloning %s to %s" url dir) (make-directory package-build-working-dir t) (let ((default-directory package-build-working-dir)) (package-build--run-process "hg" "clone" url dir)))))) ;;; Checkout (cl-defmethod package-build--checkout ((rcp package-git-recipe)) (unless package-build--inhibit-checkout (let ((rev (oref rcp commit))) (package-build--message "Checking out %s" rev) (package-build--run-process "git" "reset" "--hard" rev)))) (cl-defmethod package-build--checkout ((rcp package-hg-recipe)) (unless package-build--inhibit-checkout (let ((rev (oref rcp commit))) (package-build--message "Checking out %s" rev) (package-build--run-process "hg" "update" rev)))) ;;; Generate Files (defun package-build--write-pkg-file (desc dir) (let ((name (package-desc-name desc))) (with-temp-file (expand-file-name (format "%s-pkg.el" name) dir) (pp `(define-package ,(symbol-name name) ,(package-version-join (package-desc-version desc)) ,(package-desc-summary desc) ',(mapcar (pcase-lambda (`(,pkg ,ver)) (list pkg (package-version-join ver))) (package-desc-reqs desc)) ,@(cl-mapcan (pcase-lambda (`(,key . ,val)) (when (or (symbolp val) (listp val)) ;; We must quote lists and symbols, ;; because Emacs 24.3 and earlier evaluate ;; the package information, which would ;; break for unquoted symbols or lists. ;; While this library does not support ;; such old Emacsen, the packages that ;; we produce should remain compatible. (setq val (list 'quote val))) (list key val)) (package-desc-extras desc))) (current-buffer)) (princ ";; Local Variables:\n;; no-byte-compile: t\n;; End:\n" (current-buffer))))) (defun package-build--tar-type () "Return `bsd' or `gnu' depending on type of Tar executable. Tests and sets variable `package-build--tar-type' if not already set." (or package-build--tar-type (and package-build-tar-executable (let ((v (shell-command-to-string (format "%s --version" package-build-tar-executable)))) (setq package-build--tar-type (cond ((string-match-p "bsdtar" v) 'bsd) ((string-match-p "GNU tar" v) 'gnu) (t 'gnu))))))) (defun package-build--create-tar (rcp directory) "Create a tar file containing the package version specified by RCP. DIRECTORY is a temporary directory that contains the directory that is put in the tarball." (let* ((name (oref rcp name)) (version (oref rcp version)) (time (oref rcp time)) (tar (expand-file-name (concat name "-" version ".tar") package-build-archive-dir)) (dir (concat name "-" version))) (when (and (eq system-type 'windows-nt) (eq (package-build--tar-type) 'gnu)) (setq tar (replace-regexp-in-string "^\\([a-z]\\):" "/\\1" tar))) (let ((default-directory directory)) (process-file package-build-tar-executable nil (get-buffer-create "*package-build-checkout*") nil "-cf" tar dir ;; Arguments that are need to strip metadata that ;; prevent a reproducible tarball as described at ;; https://reproducible-builds.org/docs/archives. "--sort=name" (format "--mtime=@%d" time) "--owner=0" "--group=0" "--numeric-owner" "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime")) (when (and package-build-verbose noninteractive) (message "Created %s containing:" (file-name-nondirectory tar)) (dolist (line (sort (process-lines package-build-tar-executable "--list" "--file" tar) #'string<)) (message " %s" line))))) (defun package-build--write-pkg-readme (pkg files) (when-let* ((name (oref pkg name)) (commentary (let* ((file (concat name ".el")) (file (or (car (rassoc file files)) file)) (file (and file (expand-file-name file)))) (and (file-exists-p file) (lm-commentary file))))) (with-temp-buffer (if (>= emacs-major-version 28) (insert commentary) ;; Taken from 28.0's `lm-commentary'. (insert (replace-regexp-in-string ; Get rid of... "[[:blank:]]*$" "" ; trailing white-space (replace-regexp-in-string (format "%s\\|%s\\|%s" ;; commentary header (concat "^;;;[[:blank:]]*\\(" lm-commentary-header "\\):[[:blank:]\n]*") "^;;[[:blank:]]?" ; double semicolon prefix "[[:blank:]\n]*\\'") ; trailing new-lines "" commentary)))) (unless (or (bobp) (= (char-before) ?\n)) (insert ?\n)) ;; We write the file even if it is empty, which is perhaps ;; a questionable choice, but at least it's consistent. (let ((coding-system-for-write buffer-file-coding-system)) (write-region nil nil (expand-file-name (concat name "-readme.txt") package-build-archive-dir)))))) (defun package-build--generate-info-files (files target-dir) "Create an info file for each texinfo file listed in FILES. Also create the info dir file. Remove each original texinfo file. The source and destination file paths are expanded in `default-directory' and TARGET-DIR respectively." (pcase-dolist (`(,src . ,tmp) files) (let ((extension (file-name-extension tmp))) (when (member extension '("info" "texi" "texinfo")) (let* ((src (expand-file-name src)) (tmp (expand-file-name tmp target-dir)) (texi src) (info tmp)) (when (member extension '("texi" "texinfo")) (delete-file tmp) (setq info (concat (file-name-sans-extension tmp) ".info")) (unless (file-exists-p info) (package-build--message "Generating %s" info) ;; If the info file is located in a subdirectory ;; and contains relative includes, then it is ;; necessary to run makeinfo in the subdirectory. (with-demoted-errors "Error: %S" (let ((default-directory (file-name-directory texi))) (package-build--run-process "makeinfo" "--no-split" texi "-o" info))))) (with-demoted-errors "Error: %S" (let ((default-directory target-dir)) (package-build--run-process "install-info" "--dir=dir" info)))))))) ;;; Patch Libraries (defun package-build--update-or-insert-header (name value) "Ensure current buffer has NAME header with the given VALUE. Any existing header will be preserved and given the \"X-Original-\" prefix. If VALUE is nil, the new header will not be inserted, but any original will still be renamed." (goto-char (point-min)) (cond ((let ((case-fold-search t)) (re-search-forward (format "^;+* *%s *: *" (regexp-quote name)) nil t)) (move-beginning-of-line nil) (search-forward "V" nil t) (backward-char) (insert "X-Original-") (move-beginning-of-line nil)) (t ;; Put the new header in a sensible place if we can. (re-search-forward "^;+* *\\(Version\\|Package-Requires\\|Keywords\\|URL\\) *:" nil t) (forward-line))) (insert (format ";; %s: %s\n" name value))) (defun package-build--ensure-ends-here-line (file) "Add the \"FILE ends here\" trailing line if it is missing." (save-excursion (goto-char (point-min)) (let ((trailer (format ";;; %s ends here" (file-name-nondirectory file)))) (unless (re-search-forward (format "^%s" (regexp-quote trailer)) nil t) (goto-char (point-max)) (insert ?\n trailer ?\n))))) ;;; Package Structs (defun package-build--desc-from-library (rcp files &optional kind) "Return the package description for RCP. This function is used for all packages that consist of a single file and those packages that consist of multiple files but lack a file named \"NAME-pkg.el\" or \"NAME-pkg.el\". The returned value is a `package-desc' struct (which see). The values of the `name' and `version' slots are taken from RCP itself. The value of `kind' is taken from the KIND argument, which defaults to `single'; the other valid value being `tar'. Other information is taken from the file named \"NAME-pkg.el\", which should appear in FILES. As a fallback, \"NAME-pkg.el.in\" is also tried. If neither file exists, then return nil. If a value is not specified in the used file, then fall back to the value specified in the file \"NAME.el\"." (let* ((name (oref rcp name)) (version (oref rcp version)) (commit (oref rcp commit)) (file (concat name ".el")) (file (or (car (rassoc file files)) file)) (maintainers nil)) (and (file-exists-p file) (with-temp-buffer (insert-file-contents file) (setq maintainers (if (fboundp 'lm-maintainers) (lm-maintainers) (with-no-warnings (when-let* ((maintainer (lm-maintainer))) (list maintainer))))) (package-desc-from-define name version (or (save-excursion (goto-char (point-min)) (and (re-search-forward "\ ^;;; [^ ]*\\.el ---[ \t]*\\(.*?\\)[ \t]*\\(-\\*-.*-\\*-[ \t]*\\)?$" nil t) (match-string-no-properties 1))) "No description available.") (when-let* ((require-lines (lm-header-multiline "package-requires"))) (package--prepare-dependencies (package-read-from-string (mapconcat #'identity require-lines " ")))) ;; `:kind' and `:archive' are handled separately. :kind (or kind 'single) ;; The other keyword arguments are appended to the alist ;; stored in the `extras' slot. Make sure `:commit', which ;; always exists and never has to be removed, comes first in ;; the end result, so we can post-process the returned data ;; by side-effect, e.g., to remove somewhat broken maintainer ;; information, that cannot easily be encoded as json (see ;; `package-build--archive-alist-for-json'). :url (lm-homepage) :keywords (lm-keywords-list) ;; Newer `package.el' versions support both `:maintainers' and ;; `:maintainer', while older versions only support the latter. :maintainer (car maintainers) :maintainers maintainers :authors (lm-authors) :commit commit))))) (defun package-build--desc-from-package (rcp files) "Return the package description for RCP. This function is used for packages that consist of multiple files. The returned value is a `package-desc' struct (which see). The values of the `name' and `version' slots are taken from RCP itself. The value of `kind' is always `tar'. Other information is taken from the file named \"NAME.el\", which should appear in FILES. As a fallback, \"NAME.el.in\" is also tried. If neither file exists, then return nil." (let* ((name (oref rcp name)) (version (oref rcp version)) (commit (oref rcp commit)) (file (concat name "-pkg.el")) (file (or (car (rassoc file files)) file))) (and (or (file-exists-p file) (file-exists-p (setq file (concat file ".in")))) (let ((form (with-temp-buffer (insert-file-contents file) (read (current-buffer))))) (unless (eq (car form) 'define-package) (error "No define-package found in %s" file)) (pcase-let* ((`(,_ ,_ ,_ ,summary ,deps . ,extra) form) (deps (eval deps)) (alt-desc (package-build--desc-from-library rcp files)) (alt (and alt-desc (package-desc-extras alt-desc)))) (when (string-match "[\r\n]" summary) (error "Illegal multi-line package description in %s" file)) (package-desc-from-define name version (if (string-empty-p summary) (or (and alt-desc (package-desc-summary alt-desc)) "No description available.") summary) (mapcar (pcase-lambda (`(,pkg ,ver)) (unless (symbolp pkg) (error "Invalid package name in dependency: %S" pkg)) (list pkg ver)) deps) :kind 'tar :url (or (alist-get :url extra) (alist-get :homepage extra) (alist-get :url alt)) :keywords (or (alist-get :keywords extra) (alist-get :keywords alt)) :maintainer (or (alist-get :maintainer extra) (alist-get :maintainer alt)) :authors (or (alist-get :authors extra) (alist-get :authors alt)) :commit commit)))))) (defun package-build--write-archive-entry (desc) (with-temp-file (expand-file-name (concat (package-desc-full-name desc) ".entry") package-build-archive-dir) (set-buffer-file-coding-system 'utf-8) (pp (cons (package-desc-name desc) (vector (package-desc-version desc) (package-desc-reqs desc) (package-desc-summary desc) (package-desc-kind desc) (package-desc-extras desc))) (current-buffer)))) ;;; Files Spec (defconst package-build-default-files-spec '("*.el" "lisp/*.el" "dir" "*.info" "*.texi" "*.texinfo" "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo" "docs/dir" "docs/*.info" "docs/*.texi" "docs/*.texinfo" (:exclude ".dir-locals.el" "lisp/.dir-locals.el" "test.el" "tests.el" "*-test.el" "*-tests.el" "lisp/test.el" "lisp/tests.el" "lisp/*-test.el" "lisp/*-tests.el")) "Default value for :files attribute in recipes.") (defun package-build-expand-files-spec (rcp &optional assert repo spec) "Return an alist of files of package RCP to be included in tarball. Each element has the form (SOURCE . DESTINATION), where SOURCE is a file in the package's repository and DESTINATION is where that file is placed in the package's tarball. RCP is the package recipe as an object. If the `files' slot of RCP is non-nil, then that is used as the file specification. Otherwise `package-build-default-files-spec' is used. If optional ASSERT is non-nil, then raise an error if nil would be returned. If ASSERT and `files' are both non-nil and using `files' results in the same set of files as the default spec, then show a warning. A files specification is a list. Its elements are processed in order and can have the following form: - :defaults If the first element is `:defaults', then that means to prepend the default files spec to the SPEC specified by the remaining elements. - GLOB A string is glob-expanded to match zero or more files. Matched files are copied to the top-level directory. - (SUBDIRECTORY GLOB...) A list that begins with a string causes the files matched by the second and subsequent elements to be copied into the sub- directory specified by the first element. - (:exclude GLOB...) A list that begins with `:exclude' causes files that were matched by earlier elements that are also matched by the second and subsequent elements of this list to be removed from the returned alist. Files matched by later elements are not affected." (let ((default-directory (or repo (package-build--working-tree rcp))) (spec (or spec (oref rcp files)))) (when (eq (car spec) :defaults) (setq spec (append package-build-default-files-spec (cdr spec)))) (let ((files (package-build--expand-files-spec-1 (or spec package-build-default-files-spec)))) (when assert (when (and rcp spec (equal files (package-build--expand-files-spec-1 package-build-default-files-spec))) (message "Warning: %s :files spec is equivalent to the default" (oref rcp name))) (unless files (error "No matching file(s) found in %s using %s" default-directory (or spec "default spec")))) files))) (defun package-build--expand-files-spec-1 (spec) "Return a list of all files matching SPEC in `default-directory'. SPEC is a full files spec as stored in a recipe object." (let (include exclude) (dolist (entry spec) (if (eq (car-safe entry) :exclude) (dolist (entry (cdr entry)) (push entry exclude)) (push entry include))) (cl-set-difference (package-build--expand-files-spec-2 (nreverse include)) (package-build--expand-files-spec-2 (nreverse exclude)) :test #'equal :key #'car))) (defun package-build--expand-files-spec-2 (spec &optional subdir) "Return a list of all files matching SPEC in SUBDIR. If SUBDIR is nil, use `default-directory'. SPEC is expected to be a partial files spec, consisting of either all include rules or all exclude rules (with the `:exclude' keyword removed)." (mapcan (lambda (entry) (if (stringp entry) (mapcar (lambda (f) (cons f (concat subdir (replace-regexp-in-string "\\.el\\.in\\'" ".el" (file-name-nondirectory f))))) (file-expand-wildcards entry)) (package-build--expand-files-spec-2 (cdr entry) (concat subdir (car entry) "/")))) spec)) (defun package-build--copy-package-files (files target-dir) "Copy FILES from `default-directory' to TARGET-DIR. FILES is a list of (SOURCE . DEST) relative filepath pairs." (package-build--message "Copying files (->) and directories (=>)\n from %s\n to %s" default-directory target-dir) (pcase-dolist (`(,src . ,dst) files) (let ((src* (expand-file-name src)) (dst* (expand-file-name dst target-dir))) (make-directory (file-name-directory dst*) t) (cond ((file-regular-p src*) (package-build--message " %s %s -> %s" (if (equal src dst) " " "!") src dst) (copy-file src* dst*)) ((file-directory-p src*) (package-build--message " %s %s => %s" (if (equal src dst) " " "!") src dst) (copy-directory src* dst*)))))) (defun package-build--spec-globs (rcp) "Return a list of vcs arguments to match the files specified in RCP." ;; See glob(7), gitglossary(7) and "hg help patterns". (cl-flet ((toargs (glob &optional exclude) ;; Given an element like ("dir" "dir/*"), we want to move ;; all children of "dir" to the top-level. Glob handling ;; of git-log/hg-log only cares about regular file, so if ;; "dir/subdir/file" is modified, then "dir/*" does not ;; match that change. Use "dir/**" instead, to make them ;; look for changes to files in "dir" and all subdirs. (when (string-suffix-p "/*" glob) (setq glob (concat glob "*"))) (cl-etypecase rcp (package-git-recipe (list (format ":(glob%s)%s" (if exclude ",exclude" "") glob))) (package-hg-recipe (list (if exclude "--exclude" "--include") (concat "glob:" glob)))))) (mapcan (lambda (entry) (pcase-exhaustive entry ((and glob (pred stringp)) (toargs glob)) ((and `(:exclude . ,globs) (guard (cl-every #'stringp globs))) (mapcan (lambda (g) (toargs g t)) globs)) ((and `(,dir . ,globs) (guard (stringp dir)) (guard (cl-every #'stringp globs))) dir ; Silence byte-compiler of Emacs < 28.1. (mapcan #'toargs globs)))) (let ((spec (or (oref rcp files) package-build-default-files-spec))) (if (eq (car spec) :defaults) (append package-build-default-files-spec (cdr spec)) spec))))) ;;; Commands ;;;###autoload (defun package-build-archive (name &optional dump-archive-contents) "Build a package archive for the package named NAME. If DUMP-ARCHIVE-CONTENTS is non-nil, the updated archive contents are subsequently dumped." (interactive (list (package-recipe-read-name) t)) (unless (file-exists-p package-build-archive-dir) (package-build--message "Creating directory %s" package-build-archive-dir) (make-directory package-build-archive-dir)) (let* ((start-time (current-time)) (rcp (package-recipe-lookup name)) (url (package-recipe--upstream-url rcp)) (repo (oref rcp repo)) (fetcher (package-recipe--fetcher rcp))) (cond ((not noninteractive) (message " • %s package %s (from %s)..." (if package-build--inhibit-build "Fetching" "Building") name (if repo (format "%s:%s" fetcher repo) url))) (package-build-verbose (message "Package: %s" name) (message "Fetcher: %s" fetcher) (message "Source: %s\n" url))) (funcall package-build-fetch-function rcp) (unless package-build--inhibit-build (package-build--select-version rcp) (package-build--package rcp) (when dump-archive-contents (package-build-dump-archive-contents))) (message "%s %s in %.3fs, finished at %s" (if package-build--inhibit-build "Fetched" "Built") name (float-time (time-since start-time)) (format-time-string "%FT%T%z" nil t)))) ;;;###autoload (defun package-build--package (rcp) "Build the package version specified by RCP. Return the archive entry for the package and store the package in `package-build-archive-dir'." (let ((default-directory (package-build--working-tree rcp))) (unwind-protect (progn (funcall package-build-checkout-function rcp) (let ((files (package-build-expand-files-spec rcp t))) (cond ((= (length files) 0) (error "Unable to find files matching recipe patterns")) (package-build-build-function (funcall package-build-build-function rcp files)) ((= (length files) 1) (package-build--build-single-file-package rcp files)) (t (package-build--build-multi-file-package rcp files))) (when package-build-badge-data (package-build--write-badge-image (oref rcp name) (oref rcp version) package-build-archive-dir)))) (funcall package-build-cleanup-function rcp)))) (defun package-build--build-single-file-package (rcp files) (let* ((name (oref rcp name)) (version (oref rcp version)) (commit (oref rcp commit)) (file (caar files)) (source (expand-file-name file)) (target (expand-file-name (concat name "-" version ".el") package-build-archive-dir)) (desc (package-build--desc-from-library rcp files))) (unless (member (downcase (file-name-nondirectory file)) (list (downcase (concat name ".el")) (downcase (concat name ".el.in")))) (error "Single file %s does not match package name %s" file name)) (copy-file source target t) (let ((enable-local-variables nil) (make-backup-files nil) (before-save-hook nil)) (with-current-buffer (find-file target) (package-build--update-or-insert-header "Package-Commit" commit) (package-build--update-or-insert-header "Package-Version" version) (package-build--ensure-ends-here-line source) (write-file target nil) (kill-buffer))) (package-build--write-pkg-readme rcp files) (package-build--write-archive-entry desc))) (defun package-build--build-multi-file-package (rcp files) (let* ((name (oref rcp name)) (version (oref rcp version)) (tmp-dir (file-name-as-directory (make-temp-file name t)))) (unwind-protect (let* ((target (expand-file-name (concat name "-" version) tmp-dir)) (desc (or (package-build--desc-from-package rcp files) (package-build--desc-from-library rcp files 'tar) (error "%s[-pkg].el matching package name is missing" name)))) (package-build--copy-package-files files target) (package-build--write-pkg-file desc target) (package-build--generate-info-files files target) (package-build--create-tar rcp tmp-dir) (package-build--write-pkg-readme rcp files) (package-build--write-archive-entry desc)) (delete-directory tmp-dir t nil)))) (defun package-build--cleanup (rcp) (cond ((cl-typep rcp 'package-git-recipe) (package-build--run-process "git" "clean" "-f" "-d" "-x")) ((cl-typep rcp 'package-hg-recipe) (package-build--run-process "hg" "purge")))) ;;;###autoload (defun package-build-all () "Build a package for each of the available recipes. If `package-build-predicate-function' is non-nil, then only packages for which that returns non-nil are build." (interactive) (let* ((start (current-time)) (recipes (package-recipe-recipes)) (total (length recipes)) (success 0) skipped invalid failed) (dolist (name recipes) (let ((rcp (with-demoted-errors "Recipe error: %S" (package-recipe-lookup name)))) (cond ((not rcp) (push name invalid)) ((and package-build-predicate-function (not (funcall package-build-predicate-function rcp))) (push name skipped)) ((with-demoted-errors "Build error: %S" (package-build-archive name) t) (cl-incf success)) ((push name failed))))) (let ((duration (/ (float-time (time-subtract (current-time) start)) 60))) (if (not (or skipped invalid failed)) (message "Successfully built all %s packages (%.0fm)" total duration) (message "Successfully built %i of %s packages (%.0fm)" success total duration) (when skipped (message "Skipped %i packages:\n%s" (length skipped) (mapconcat (lambda (n) (concat " " n)) (nreverse skipped) "\n"))) (when invalid (message "Did not built packages for %i invalid recipes:\n%s" (length invalid) (mapconcat (lambda (n) (concat " " n)) (nreverse invalid) "\n"))) (when failed (message "Building %i packages failed:\n%s" (length failed) (mapconcat (lambda (n) (concat " " n)) (nreverse failed) "\n")))))) (package-build-dump-archive-contents)) (defun package-build-cleanup () "Remove previously built packages that no longer have recipes." (interactive) (package-build-dump-archive-contents)) ;;; Archive (defun package-build-archive-alist () "Return the archive contents, without updating it first." (let ((file (expand-file-name "archive-contents" package-build-archive-dir))) (and (file-exists-p file) (with-temp-buffer (insert-file-contents file) (cdr (read (current-buffer))))))) (defun package-build-dump-archive-contents (&optional file pretty-print) "Update and return the archive contents. Update files \"archive-contents\" and \"elpa-packages.eld\" in `package-build-archive-dir'. If optional FILE is non-nil, use that to store the archive contents and place the second file next to it. If optional PRETTY-PRINT is non-nil, then pretty-print \"archive-contents\" instead of using one line per entry. \"elpa-packages.eld\" always uses one line per entry." (let ((default-directory package-build-archive-dir) (entries nil) (vc-pkgs nil)) (dolist (file (sort (directory-files default-directory t ".*\\.entry\\'") ;; Sort more recently build packages first. #'file-newer-than-file-p)) (let* ((entry (with-temp-buffer (insert-file-contents file) (read (current-buffer)))) (symbol (car entry)) (name (symbol-name symbol)) (outdated (assq symbol entries))) (cond ((not (file-exists-p (expand-file-name name package-build-recipes-dir))) ;; Recipe corresponding to this entry no longer exists. (package-build--remove-archive-files entry)) (outdated ;; Prefer the more recently built package, which may not ;; necessarily have the highest version number, e.g., if ;; commit histories were changed. (package-build--remove-archive-files entry)) (t (push entry entries) ;; [Non]GNU ELPA recipes are not compatible with Melpa recipes. ;; See around occurrences of "pkg-spec" in "package-vc.el"; ;; section "Specifications (elpa-packages)" in "README" of the ;; "elpa-admin" branch in "emacs/elpa.git" repository; and also ;; `elpaa--supported-keywords' and `elpaa--publish-package-spec'. (let ((recipe (package-recipe-lookup name))) (push `(,symbol :url ,(package-recipe--upstream-url recipe) ,@(and (cl-typep recipe 'package-hg-recipe) (list :vc-backend 'Hg)) ,@(when-let* ((branch (oref recipe branch))) (list :branch branch))) vc-pkgs)))))) (setq entries (cl-sort entries #'string< :key (lambda (e) (symbol-name (car e))))) (with-temp-file (or file (expand-file-name "archive-contents")) (let ((print-level nil) (print-length nil)) (if pretty-print (pp (cons 1 entries) (current-buffer)) (insert "(1") (dolist (entry entries) (newline) (insert " ") (prin1 entry (current-buffer))) (insert ")\n")))) (with-temp-file (expand-file-name "elpa-packages.eld" (and file (file-name-nondirectory file))) (let ((print-level nil) (print-length nil)) (insert "((") (prin1 (car vc-pkgs) (current-buffer)) (dolist (entry (cdr vc-pkgs)) (newline) (insert " ") (prin1 entry (current-buffer))) (insert ")\n :version 1 :default-vc Git)\n"))) entries)) (defun package-build--remove-archive-files (archive-entry) "Remove the entry and archive file for ARCHIVE-ENTRY." (package-build--message "Removing archive: %s-%s" (car archive-entry) (package-version-join (aref (cdr archive-entry) 0))) (let ((file (package-build--artifact-file archive-entry))) (when (file-exists-p file) (delete-file file))) (let ((file (package-build--archive-entry-file archive-entry))) (when (file-exists-p file) (delete-file file)))) (defun package-build--artifact-file (archive-entry) "Return the artifact file for the package specified by ARCHIVE-ENTRY. This is either a tarball or an Elisp file." (pcase-let* ((`(,name . ,desc) archive-entry) (version (package-version-join (aref desc 0))) (flavour (aref desc 3))) (expand-file-name (format "%s-%s.%s" name version (if (eq flavour 'single) "el" "tar")) package-build-archive-dir))) (defun package-build--archive-entry-file (archive-entry) "Return the file in which ARCHIVE-ENTRY should be stored. ARCHIVE-ENTRY contains information about a specific version of a package." (pcase-let* ((`(,name . ,desc) archive-entry) (version (package-version-join (aref desc 0)))) (expand-file-name (format "%s-%s.entry" name version) package-build-archive-dir))) ;;; Json Exports (defun package-build-recipe-alist-as-json (file) "Dump the recipe list to FILE as json." (interactive "FDump json to file: ") (with-temp-file file (insert (json-encode (cl-mapcan (lambda (name) (ignore-errors ; Silently ignore corrupted recipes. (and (package-recipe-lookup name) (with-temp-buffer (insert-file-contents (expand-file-name name package-build-recipes-dir)) (let ((exp (read (current-buffer)))) (when (plist-member (cdr exp) :files) (plist-put (cdr exp) :files (format "%S" (plist-get (cdr exp) :files)))) (list exp)))))) (package-recipe-recipes)))))) (defun package-build--pkg-info-for-json (info) "Convert INFO so that it can be serialize to JSON in the desired shape." (pcase-let ((`(,ver ,deps ,desc ,type . (,props)) (append info nil))) (list :ver ver :deps (cl-mapcan (lambda (dep) (list (intern (format ":%s" (car dep))) (cadr dep))) deps) :desc desc :type type :props props))) (defun package-build--archive-alist-for-json () "Return the archive alist in a form suitable for JSON encoding." (cl-flet ((format-person (person) (let ((name (car person)) (mail (cdr person))) (if (and name mail) (format "%s <%s>" name mail) (or name (format "<%s>" mail)))))) (cl-mapcan (lambda (entry) (list (intern (format ":%s" (car entry))) (let* ((info (cdr entry)) (extra (aref info 4)) (maintainer (assq :maintainer extra)) (maintainers (assq :maintainers extra)) (authors (assq :authors extra))) (when maintainer (setcdr maintainer (format-person (cdr maintainer)))) (when maintainers (if (cl-every #'listp (cdr maintainers)) (setcdr maintainers (mapcar #'format-person (cdr maintainers))) (setq maintainers ; silence >= 30 compiler (assq-delete-all :maintainers extra)))) (when authors (if (cl-every #'listp (cdr authors)) (setcdr authors (mapcar #'format-person (cdr authors))) (setq authors ; silence >= 30 compiler (assq-delete-all :authors extra)))) (package-build--pkg-info-for-json info)))) (package-build-archive-alist)))) (defun package-build-archive-alist-as-json (file) "Dump the build packages list to FILE as json." (with-temp-file file (insert (json-encode (package-build--archive-alist-for-json))))) ;;; _ (provide 'package-build) ;;; package-build.el ends here ================================================ FILE: lisp/extern/package-build/26/package-recipe-mode.el ================================================ ;;; package-recipe-mode.el --- Major-mode for editing package recipes -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2011-2023 Donald Ephraim Curtis ;; Copyright (C) 2012-2023 Steve Purcell ;; Copyright (C) 2016-2023 Jonas Bernoulli ;; Copyright (C) 2009 Phil Hagelberg ;; Author: Donald Ephraim Curtis ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; This library defines the major-mode `package-recipe-mode', which is ;; used for Melpa package recipe files. ;;; Code: (require 'package-build) (defvar flycheck-checkers) ;;;###autoload (defvar package-recipe-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-c") 'package-build-current-recipe) (define-key map (kbd "C-c C-n") 'package-build-create-recipe) map) "Keymap for `package-recipe-mode'.") ;;;###autoload (if (fboundp 'lisp-data-mode) ; Since Emacs 28.1. (define-derived-mode package-recipe-mode lisp-data-mode "Melpa-Recipe" "Major mode for buffers holding Melpa package recipes." :group 'package-build (package-recipe-mode--enable)) (define-derived-mode package-recipe-mode emacs-lisp-mode "Melpa-Recipe" "Major mode for buffers holding Melpa package recipes." :group 'package-build (package-recipe-mode--enable))) (defun package-recipe-mode--enable () (setq-local package-build-recipes-dir default-directory) (setq-local package-build-working-dir (expand-file-name "../working/")) (setq-local package-build-archive-dir (expand-file-name "../packages/")) (setq-local flycheck-checkers nil) (setq-local indent-tabs-mode nil) (setq-local require-final-newline t) (add-hook 'before-save-hook #'whitespace-cleanup nil t) (message "%s" (substitute-command-keys "\ Use \\[package-build-current-recipe] to build this recipe, \ \\[package-build-create-recipe] to create a new recipe"))) ;;;###autoload (defun package-build-create-recipe (name fetcher) "Create a new recipe for the package named NAME using FETCHER." (interactive (list (read-string "Package name: ") (intern (completing-read "Fetcher: " package-recipe--fetchers nil t nil nil "github")))) (let ((recipe-file (expand-file-name name package-build-recipes-dir))) (when (file-exists-p recipe-file) (error "Recipe already exists")) (with-current-buffer (find-file recipe-file) (save-excursion (insert (format "(%s\n" name) (format " :fetcher %s\n" fetcher) (if (memq fetcher package-recipe--forge-fetchers) " :repo \"USER/REPO\")\n" " :url \"https://TODO\")\n")))))) ;;;###autoload (defun package-build-current-recipe () "Build archive for the recipe defined in the current buffer." (interactive) (unless (and (buffer-file-name) (file-equal-p (file-name-directory (buffer-file-name)) package-build-recipes-dir)) (error "Buffer is not visiting a recipe")) (when (buffer-modified-p) (if (y-or-n-p (format "Save file %s? " buffer-file-name)) (save-buffer) (error "Aborting"))) (check-parens) (let ((name (file-name-nondirectory (buffer-file-name)))) (package-build-archive name t) (let ((entry (assq (intern name) (package-build-archive-alist))) (output-buffer-name "*package-build-archive-entry*")) (with-output-to-temp-buffer output-buffer-name (princ ";; Please check the following package descriptor.\n") (princ ";; If the correct package description or dependencies are missing,\n") (princ ";; then the source .el file is likely malformed, and should be fixed.\n") (pp entry)) (with-current-buffer output-buffer-name (if (fboundp 'lisp-data-mode) (lisp-data-mode) (emacs-lisp-mode)) (view-mode)) (when (y-or-n-p "Install new package? ") (package-install-file (package-build--artifact-file entry)) (pop-to-buffer (get-buffer byte-compile-log-buffer)))))) (provide 'package-recipe-mode) ;;; package-recipe-mode.el ends here ================================================ FILE: lisp/extern/package-build/26/package-recipe.el ================================================ ;;; package-recipe.el --- Package recipes as EIEIO objects -*- lexical-binding:t; coding:utf-8 -*- ;; Copyright (C) 2018-2023 Jonas Bernoulli ;; Author: Jonas Bernoulli ;; Homepage: https://github.com/melpa/package-build ;; Keywords: maint tools ;; SPDX-License-Identifier: GPL-3.0-or-later ;; This file is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published ;; by the Free Software Foundation, either version 3 of the License, ;; or (at your option) any later version. ;; ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; Package recipes as EIEIO objects. ;;; Code: (require 'eieio) (require 'subr-x) (require 'url-parse) (defvar package-build-use-git-remote-hg) (defvar package-build-recipes-dir) (defvar package-build-working-dir) ;;; Classes (defclass package-recipe () ((url-format :allocation :class :initform nil) (repopage-format :allocation :class :initform nil) (stable-p :allocation :class :initform nil) (name :initarg :name :initform nil) (url :initarg :url :initform nil) (repo :initarg :repo :initform nil) (repopage :initarg :repopage :initform nil) (files :initarg :files :initform nil) (branch :initarg :branch :initform nil) (commit :initarg :commit :initform nil) (time :initform nil) (version :initform nil) (version-regexp :initarg :version-regexp :initform nil) (old-names :initarg :old-names :initform nil)) :abstract t) ;;;; Git (defclass package-git-recipe (package-recipe) ()) (defclass package-github-recipe (package-git-recipe) ((url-format :initform "https://github.com/%s.git") (repopage-format :initform "https://github.com/%s"))) (defclass package-gitlab-recipe (package-git-recipe) ((url-format :initform "https://gitlab.com/%s.git") (repopage-format :initform "https://gitlab.com/%s"))) (defclass package-codeberg-recipe (package-git-recipe) ((url-format :initform "https://codeberg.org/%s.git") (repopage-format :initform "https://codeberg.org/%s"))) (defclass package-sourcehut-recipe (package-git-recipe) ((url-format :initform "https://git.sr.ht/~%s") (repopage-format :initform "https://git.sr.ht/~%s"))) ;;;; Mercurial (defclass package-hg-recipe (package-recipe) ()) (defclass package-git-remote-hg-recipe (package-git-recipe) ()) ;;; Methods (cl-defmethod package-recipe--working-tree ((rcp package-recipe)) (file-name-as-directory (expand-file-name (oref rcp name) package-build-working-dir))) (cl-defmethod package-recipe--upstream-url ((rcp package-recipe)) (or (oref rcp url) (format (oref rcp url-format) (oref rcp repo)))) (cl-defmethod package-recipe--upstream-url ((rcp package-git-remote-hg-recipe)) (concat "hg::" (oref rcp url))) (cl-defmethod package-recipe--upstream-protocol ((rcp package-recipe)) (let ((url (package-recipe--upstream-url rcp))) (cond ((string-match "\\`\\([a-z]+\\)://" url) (match-string 1 url)) ((string-match "\\`[^:/ ]+:" url) "ssh") (t "file")))) (cl-defmethod package-recipe--fetcher ((rcp package-recipe)) (substring (symbol-name (eieio-object-class rcp)) 8 -7)) ;;; Constants (defconst package-recipe--forge-fetchers '(github gitlab codeberg sourcehut)) (defconst package-recipe--fetchers (append '(git hg) package-recipe--forge-fetchers)) ;;; Interface (defun package-recipe-recipes () "Return a list of the names of packages with available recipes." (directory-files package-build-recipes-dir nil "^[^.]")) (defun package-recipe-read-name () "Read the name of a package for which a recipe is available." (completing-read "Package: " (package-recipe-recipes))) (defun package-recipe-lookup (name) "Return a recipe object for the package named NAME. If no such recipe file exists or if the contents of the recipe file is invalid, then raise an error." (let ((file (expand-file-name name package-build-recipes-dir))) (if (file-exists-p file) (let* ((recipe (with-temp-buffer (insert-file-contents file) (read (current-buffer)))) (plist (cdr recipe)) (fetcher (plist-get plist :fetcher)) key val args) (package-recipe--validate recipe name) (while (and (setq key (pop plist)) (setq val (pop plist))) (unless (eq key :fetcher) (push val args) (push key args))) (when (and package-build-use-git-remote-hg (eq fetcher 'hg)) (setq fetcher 'git-remote-hg)) (apply (intern (format "package-%s-recipe" fetcher)) name :name name args)) (error "No such recipe: %s" name)))) ;;; Validation (defun package-recipe-validate-all () "Validate all recipes." (interactive) (dolist (name (package-recipe-recipes)) (condition-case err (package-recipe-lookup name) (error (message "Invalid recipe for %s: %S" name (cdr err)))))) (defun package-recipe--validate (recipe name) "Perform some basic checks on the raw RECIPE for the package named NAME." (pcase-let ((`(,ident . ,plist) recipe)) (cl-assert ident) (cl-assert (symbolp ident)) (cl-assert (string= (symbol-name ident) name) nil "Recipe '%s' contains mismatched package name '%s'" name ident) (cl-assert plist) (let* ((symbol-keys '(:fetcher)) (string-keys '(:url :repo :commit :branch :version-regexp)) (list-keys '(:files :old-names)) (all-keys (append symbol-keys string-keys list-keys))) (dolist (thing plist) (when (keywordp thing) (cl-assert (memq thing all-keys) nil "Unknown keyword %S" thing))) (let ((fetcher (plist-get plist :fetcher))) (cl-assert fetcher nil ":fetcher is missing") (if (memq fetcher package-recipe--forge-fetchers) (progn (cl-assert (plist-get plist :repo) ":repo is missing") (cl-assert (not (plist-get plist :url)) ":url is redundant")) (cl-assert (plist-get plist :url) ":url is missing"))) (dolist (key symbol-keys) (let ((val (plist-get plist key))) (when val (cl-assert (symbolp val) nil "%s must be a symbol but is %S" key val)))) (dolist (key list-keys) (let ((val (plist-get plist key))) (when val (cl-assert (listp val) nil "%s must be a list but is %S" key val)))) (dolist (key string-keys) (let ((val (plist-get plist key))) (when val (cl-assert (stringp val) nil "%s must be a string but is %S" key val)))) (when-let* ((spec (plist-get plist :files))) ;; `:defaults' is only allowed as the first element. ;; If we find it in that position, skip over it. (when (eq (car spec) :defaults) (setq spec (cdr spec))) ;; All other elements have to be strings or lists of strings. ;; A list whose first element is `:exclude' is also valid. (dolist (entry spec) (unless (or (and (stringp entry) (not (equal entry "*"))) (and (listp entry) (or (eq (car entry) :exclude) (stringp (car entry))) (seq-every-p (lambda (e) (and (stringp e) (not (equal e "*")))) (cdr entry)))) (error "Invalid files spec entry %S" entry)))) ;; Silence byte compiler of Emacs 28. It appears that uses ;; inside cl-assert sometimes, but not always, do not count. (list name ident all-keys)) recipe)) (provide 'package-recipe) ;;; package-recipe.el ends here ================================================ FILE: lisp/extern/package-build/README.md ================================================ # package-build Here, we place the legacy [package-build][] package. [package-build]: https://github.com/melpa/package-build ================================================ FILE: lisp/extern/package-build.el ================================================ ;;; extern/package-build.el --- External module `package-build' -*- lexical-binding: t; -*- ;;; Commentary: ;;; Code: (require 'package-build nil t) ;; ;; NOTE: Following code are brought in cuz it's very useful, but we don't want ;; to bring the whole `package-build' package unless it's needed. ;; (defconst package-build-default-files-spec '("*.el" "lisp/*.el" "dir" "*.info" "*.texi" "*.texinfo" "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo" "docs/dir" "docs/*.info" "docs/*.texi" "docs/*.texinfo" (:exclude ".dir-locals.el" "lisp/.dir-locals.el" "test.el" "tests.el" "*-test.el" "*-tests.el" "lisp/test.el" "lisp/tests.el" "lisp/*-test.el" "lisp/*-tests.el")) "Default value for :files attribute in recipes.") (defun package-build-expand-files-spec (rcp &optional assert repo spec) "No documentation." (let ((default-directory (or repo (package-recipe--working-tree rcp))) (spec (or spec (oref rcp files)))) (when (eq (car spec) :defaults) (setq spec (append package-build-default-files-spec (cdr spec)))) (let ((files (package-build--expand-files-spec-1 (or spec package-build-default-files-spec)))) (when assert (when (and rcp spec (equal files (package-build--expand-files-spec-1 package-build-default-files-spec))) (message "Warning: %s :files spec is equivalent to the default" (oref rcp name))) (unless files (error "No matching file(s) found in %s using %s" default-directory (or spec "default spec")))) files))) (defun package-build--expand-files-spec-1 (spec &optional subdir) (let ((files nil)) (dolist (entry spec) (setq files (cond ((stringp entry) (nconc files (mapcar (lambda (f) (cons f (concat subdir (replace-regexp-in-string "\\.el\\.in\\'" ".el" (file-name-nondirectory f))))) (file-expand-wildcards entry)))) ((eq (car entry) :exclude) (cl-nset-difference files (package-build--expand-files-spec-1 (cdr entry)) :key #'car :test #'equal)) (t (nconc files (package-build--expand-files-spec-1 (cdr entry) (concat subdir (car entry) "/"))))))) files)) ;;; extern/package-build.el ends here ================================================ FILE: lisp/extern/package-recipe.el ================================================ ;;; extern/package-recipe.el --- External module `package-recipe' -*- lexical-binding: t; -*- ;;; Commentary: ;;; Code: (require 'package-recipe nil t) ;; Specializations of package-build classes and methods to define a ;; directory based recipe. (defclass package-directory-recipe (package-recipe) ((dir :initarg :dir :initform "."))) (cl-defmethod package-recipe--working-tree ((rcp package-directory-recipe)) (oref rcp dir)) (cl-defmethod package-build--get-commit ((_rcp package-directory-recipe))) ;; After https://github.com/melpa/package-build/commit/f032c806169b0fabbcac1eb44a4f1ae00674bfa8 (cl-defmethod package-build--get-commit-time ((_rcp package-directory-recipe) _rev) (eask-current-time)) ;; After https://github.com/melpa/package-build/pull/67 (cl-defmethod package-build--checkout ((rcp package-directory-recipe))) (cl-defmethod package-build--get-timestamp-version ((rcp package-directory-recipe)) (package-build--get-commit-time rcp nil)) ;;; extern/package-recipe.el ends here ================================================ FILE: lisp/extern/package.el ================================================ ;;; extern/package.el --- External module `package' -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; This module is used to compatible with older Emacs version. ;; ;;; Code: (eask-defvc< 28 (defvar package-quickstart-file (locate-user-emacs-file "package-quickstart.el")) (defun package--alist () "Return `package-alist', after computing it if needed." (or package-alist (progn (package-load-all-descriptors) package-alist))) (defun package--activate-all () (dolist (elt (package--alist)) (condition-case err (package-activate (car elt)) ;; Don't let failure of activation of a package arbitrarily stop ;; activation of further packages. (error (message "%s" (error-message-string err)))))) (defun package-activate-all () "Activate all installed packages. The variable `package-load-list' controls which packages to load." (setq package--activated t) (let* ((elc (concat package-quickstart-file "c")) (qs (if (file-readable-p elc) elc (if (file-readable-p package-quickstart-file) package-quickstart-file)))) (if qs ;; Skip load-source-file-function which would slow us down by a factor ;; 2 when loading the .el file (this assumes we were careful to ;; save this file so it doesn't need any decoding). (let ((load-source-file-function nil)) (unless (boundp 'package-activated-list) (setq package-activated-list nil)) (load qs nil 'nomessage)) (require 'package) (package--activate-all))))) ;;; extern/package.el ends here ================================================ FILE: lisp/format/elfmt.el ================================================ ;;; format/elfmt.el --- Run elfmt -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `elfmt' for all files ;; ;; $ eask format elfmt [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want elfmt to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function elfmt-buffer "ext:elisp-autofmt.el") ;; ;;; Core (defconst eask-format-elfmt--version nil "`elfmt' version.") (defun eask-format-elfmt--file (filename) "Run elfmt on FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename))) (with-current-buffer (find-file filename) (eask-ignore-errors (elfmt-buffer)) (save-buffer) (kill-buffer)))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa" "jcs-elpa") 'elfmt) (setq eask-format-elfmt--version (eask-package--version-string 'elfmt)) ;; Start formatting (require 'elfmt) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (eask-msg "") (eask-msg "Running `%s` formatter (%s)" (ansi-green "elfmt") (ansi-yellow eask-format-elfmt--version)) (eask-progress-seq " - Formatting" files "done! ✓" #'eask-format-elfmt--file) (eask-msg "") (eask-info "(Total of %s file%s %s formatted)" (length files) (eask--sinr files "" "s") (eask--sinr files "has" "have"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been formatted)") (eask-help "format/elfmt"))))) ;;; format/elfmt.el ends here ================================================ FILE: lisp/format/elisp-autofmt.el ================================================ ;;; format/elisp-autofmt.el --- Run elisp-autofmt -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `elisp-autofmt' for all files ;; ;; $ eask format elisp-autofmt [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want elisp-autofmt to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function elisp-autofmt-buffer "ext:elisp-autofmt.el") ;; ;;; Flags (eask-command-check "29.1") ;; ;;; Core (defconst eask-format-elisp-autofmt--version nil "`elisp-autofmt' version.") (defun eask-format-elisp-autofmt--file (filename) "Run elisp-autofmt on FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename))) (with-current-buffer (find-file filename) (eask-ignore-errors (elisp-autofmt-buffer)) (save-buffer) (kill-buffer)))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'elisp-autofmt) (setq eask-format-elisp-autofmt--version (eask-package--version-string 'elisp-autofmt)) ;; Start formatting (require 'elisp-autofmt) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (eask-msg "") (eask-msg "Running `%s` formatter (%s)" (ansi-green "elisp-autofmt") (ansi-yellow eask-format-elisp-autofmt--version)) (eask-progress-seq " - Formatting" files "done! ✓" #'eask-format-elisp-autofmt--file) (eask-msg "") (eask-info "(Total of %s file%s %s formatted)" (length files) (eask--sinr files "" "s") (eask--sinr files "has" "have"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been formatted)") (eask-help "format/elisp-autofmt"))))) ;;; format/elisp-autofmt.el ends here ================================================ FILE: lisp/generate/autoloads.el ================================================ ;;; generate/autoloads.el --- Generate autoload file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate autoload file, ;; ;; $ eask generate autoloads ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let* ((name (eask-guess-package-name)) (pkg-dir (or eask-package-file default-directory)) (pkg-dir (file-name-directory eask-package-file)) (autoloads-file (expand-file-name (concat name "-autoloads.el") pkg-dir))) (package-generate-autoloads name pkg-dir) (eask-msg "") (eask-info "(Generated -autoloads.el file in %s)" autoloads-file))) ;;; generate/autoloads.el ends here ================================================ FILE: lisp/generate/ignore.el ================================================ ;;; generate/ignore.el --- Generate ignore file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate ignore file, ;; ;; $ eask generate ignore ;; ;; ;; Positionals: ;; ;; Name of the ignore template ;; ;; Optional arguments: ;; ;; --output Output result to a file; the default is `.gitignore` ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function gitignore-templates-names "ext:license-templates.el") ;; ;;; Core (defun eask-generate-ignore--print-menu () "Print all available ignore." (eask-msg "available via `eask generate ignore`") (eask-msg "") (let ((names (gitignore-templates-names))) (dolist (data names) (eask-msg " %s" data)) (eask-msg "") (eask-info "(Total of %s available ignore file%s)" (length names) (eask--sinr names "" "s")))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'gitignore-templates) ;; Start the task (require 'gitignore-templates) (let* ((name (car (eask-args))) ; type of the ignore (basename (or (eask-output) ".gitignore")) (filename (expand-file-name basename))) (eask-msg "") (cond ((file-exists-p filename) (eask-info "(The ignore file already exists `%s`)" filename)) ((not (member name (eask-with-verbosity 'debug (gitignore-templates-names)))) (eask-info "(Invalid ignore type: `%s`)" name) (eask-generate-ignore--print-menu)) (t (eask-with-progress (format " - [1/1] Generating ignore file in %s... " filename) (eask-with-verbosity 'debug (with-current-buffer (find-file filename) (gitignore-templates-insert name) (save-buffer))) "done ✓") (eask-msg "") (eask-info "(See created file in `%s`)" filename))))) ;;; generate/ignore.el ends here ================================================ FILE: lisp/generate/license.el ================================================ ;;; generate/license.el --- Generate license file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate LICENSE file, ;; ;; $ eask generate license ;; ;; ;; Positionals: ;; ;; Name of the license ;; ;; Optional arguments: ;; ;; --output Output result to a file; the default is `LICENSE` ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (defvar license-templates--data) (declare-function license-templates-keys "ext:license-templates.el") ;; ;;; Core (defun eask-generate-license--print-menu () "Print all available license." (eask-msg "available via `eask generate license`") (eask-msg "") (let* ((names (license-templates-keys)) (offset (eask-seq-str-max names)) (fmt (concat " %-" (eask-2str offset) "s %s"))) (dolist (data license-templates--data) (eask-msg fmt (plist-get data :key) (plist-get data :name))) (eask-msg "") (eask-info "(Total of %s available license%s)" (length names) (eask--sinr names "" "s")))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'license-templates) ;; Start the task (require 'license-templates) (let* ((name (car (eask-args))) ; type of the license (basename (or (eask-output) "LICENSE")) (filename (expand-file-name basename))) (license-templates-keys) ; trigger request (eask-msg "") (cond ((file-exists-p filename) (eask-info "(The license file already exists `%s`)" filename)) ((not (member name (license-templates-keys))) (eask-info "(Invalid license type: `%s`)" name) (eask-generate-license--print-menu)) (t (eask-with-progress (format " - [1/1] Generating license file in %s... " filename) (with-current-buffer (find-file filename) (license-templates-insert name) (save-buffer)) "done ✓") (eask-msg "") (eask-info "(See created file in `%s`)" filename))))) ;;; generate/license.el ends here ================================================ FILE: lisp/generate/pkg-file.el ================================================ ;;; generate/pkg-file.el --- Generate -pkg file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate -pkg file, ;; ;; $ eask generate pkg-file ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-generate-pkg-file--filename) (defun eask-generate-pkg-file--from-pkg-desc () "Generate pkg-file from a package-descriptor." (let* ((name (package-desc-name eask-package-desc)) (pkg-file (expand-file-name (format "%s-pkg.el" name)))) (setq eask-generate-pkg-file--filename pkg-file) (package-generate-description-file eask-package-desc pkg-file))) (defun eask-generate-pkg-file--from-eask-file () "Generate pkg-file from Eask file." (let* ((name (eask-guess-package-name)) (pkg-file (expand-file-name (concat name "-pkg.el"))) (version (eask-package-version)) (description (eask-package-description)) (reqs (mapcar (lambda (elm) (list (eask-intern (car elm)) (if (= (length (cdr elm)) 1) (nth 0 (cdr elm)) "0"))) (append eask-depends-on-emacs eask-depends-on)))) (setq eask-generate-pkg-file--filename pkg-file) (write-region (pp-to-string `(define-package ,name ,version ,description ',reqs)) nil pkg-file))) (eask-start (if eask-package-desc (eask-generate-pkg-file--from-pkg-desc) (eask-generate-pkg-file--from-eask-file)) (eask-info "%s:" (file-name-nondirectory (directory-file-name eask-generate-pkg-file--filename))) (eask-msg "") (eask-msg "%s" (with-temp-buffer (emacs-lisp-mode) (insert-file-contents eask-generate-pkg-file--filename) (pp-buffer) (eask--silent (indent-region (point-min) (point-max))) (buffer-string))) (eask-info "(Generated -pkg.el file in %s)" eask-generate-pkg-file--filename)) ;;; generate/pkg-file.el ends here ================================================ FILE: lisp/generate/recipe.el ================================================ ;;; generate/recipe.el --- Generate recipe file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate recipe file, ;; ;; $ eask generate recipe [destination] ;; ;; ;; Positional options: ;; ;; [destination] destination path/folder ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "core/recipe") ;; ;;; Core (eask-start (if-let* ((recipe (eask-recipe-string)) (name (eask-guess-package-name))) (let* ((eask-recipe-path (or (eask-args 0) eask-recipe-path)) (eask-recipe-path (expand-file-name eask-recipe-path)) (recipe-file (expand-file-name name eask-recipe-path)) (recipe-string (pp-to-string recipe))) (when (or (eask-yes-p) (yes-or-no-p (format "%s\nIs this OK? " recipe-string))) ;; XXX: Just to fake the user input! (when (eask-yes-p) (eask-msg (format "%s\nIs this OK? (yes or no) yes" recipe-string))) (ignore-errors (make-directory eask-recipe-path t)) (with-current-buffer (find-file recipe-file) (erase-buffer) (insert recipe-string) (save-buffer)) (eask-info "(Generated in %s)" recipe-file))) (eask-msg "") (eask-info "(Repository URL is required to form a recipe)") (eask-help "core/recipe"))) ;;; generate/recipe.el ends here ================================================ FILE: lisp/generate/test/buttercup.el ================================================ ;;; generate/test/buttercup.el --- Create a new Buttercup setup for the project -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to create a new Buttercup setup for the project, ;; ;; $ eask generate test buttercup ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-generate-test-buttercup--init (&optional name) "Create new test buffercup project (optional project NAME)." (let ((name (or name (f-filename default-directory))) (test-path (expand-file-name "tests" default-directory))) (when (f-dir? test-path) (eask-error "Directory `tests` already exists.")) (message "create %s" (ansi-green (f-filename test-path))) (f-mkdir "tests") (let ((test-file (s-concat "test-" name ".el"))) (message "create %s" (ansi-green test-file)) (with-temp-file (f-join test-path test-file) (insert (format "\ ;;; %s --- Buttercup tests for %s -*- lexical-binding: t; -*- ;;; Commentary: ;;; Code: (require 'buttercup) ;; Example test! (describe \"A suite\" (it \"contains a spec with an expectation\" (expect t :to-be t))) ;;; %s ends here " test-file name test-file)))))) (eask-start (eask-archive-install-packages '("gnu" "melpa") 'buttercup 'f) (require 'buttercup) (require 'f) (eask-generate-test-buttercup--init (eask-guess-package-name))) ;;; generate/test/buttercup.el ends here ================================================ FILE: lisp/generate/test/ecukes.el ================================================ ;;; generate/test/ecukes.el --- Create a new Ecukes setup for the project -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to create a new Ecukes setup for the project, ;; ;; $ eask generate test ecukes ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (eask-archive-install-packages '("gnu" "melpa") 'ecukes) (require 'ecukes-new) (ecukes-new)) ;;; generate/test/ecukes.el ends here ================================================ FILE: lisp/generate/test/ert-runner.el ================================================ ;;; generate/test/ert-runner.el --- Create a new test project for the ert-runner -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to create a new test project for the ert-runner, ;; ;; $ eask generate test ert-runner [names..] ;; ;; ;; Positionals: ;; ;; [names..] specify test names ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "generate/test/ert") (defun eask-generate-test-ert-runner--test-helper (&optional name) "Generate test helper for NAME." (with-temp-file (f-join ert-runner-test-path "test-helper.el") (insert (format "\ ;;; test-helper.el --- Helpers for %s ;;; test-helper.el ends here " name)))) (eask-start (eask-archive-install-packages '("gnu" "melpa") 'ert-runner) (advice-add 'ert-runner/run :override #'ignore) (load-library "ert-runner") (load-library "f") (let ((name (eask-guess-package-name))) (eask-generate-test-ert--init name) (eask-generate-test-ert-runner--test-helper name)) (mapc #'eask-generate-test-ert--create-test-file (eask-args))) ;;; generate/test/ert-runner.el ends here ================================================ FILE: lisp/generate/test/ert.el ================================================ ;;; generate/test/ert.el --- Create a new test project for the ert tests -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to create a new test project for the ert tests, ;; ;; $ eask generate test ert [names..] ;; ;; ;; Positionals: ;; ;; [names..] specify test names ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defvar eask-generate-test-ert-test-path (expand-file-name "test" default-directory) "The default test path.") (defun eask-generate-test-ert--init (&optional name) "Create new test project (optional project NAME)." (let* ((name (or name (file-name-nondirectory default-directory))) (dir (file-directory-p eask-generate-test-ert-test-path))) (eask-with-progress (format "create %s folder... " (ansi-green "test")) (ignore-errors (make-directory eask-generate-test-ert-test-path t)) (if dir "skipped ✗" "done ✓")) (eask-generate-test-ert--create-test-file name))) (defun eask-generate-test-ert--create-test-file (name) "Generate test file by NAME." (let* ((test-file (concat name "-test.el")) (full-test-file (expand-file-name test-file eask-generate-test-ert-test-path)) (ext (file-exists-p full-test-file))) (eask-with-progress (format " create %s... " (ansi-green test-file)) (with-temp-file full-test-file (insert (format "\ ;;; %s --- Tests for %s ;;; %s ends here " test-file name test-file))) (if ext "skipped ✗" "done ✓")))) (eask-start (eask-generate-test-ert--init (eask-guess-package-name)) (mapc #'eask-generate-test-ert--create-test-file (eask-args))) ;;; generate/test/ert.el ends here ================================================ FILE: lisp/generate/workflow/circle-ci.el ================================================ ;;; generate/workflow/circle-ci.el --- Generate CircleCI workflow yaml file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate CircleCI test yaml file, ;; ;; $ eask generate workflow circle-ci [file] ;; ;; ;; Positionals: ;; ;; [file] name of the test file; the default is `config.yml` ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-generate-workflow-circle-ci--insert-jobs (version) "Insert Circle CI's jobs instruction for specific Emacs' VERSION." (insert " test-ubuntu-emacs-" version ":" "\n") (insert " docker:" "\n") (insert " - image: silex/emacs:" version "-ci" "\n") (insert " entrypoint: bash" "\n") (insert " steps:" "\n") (insert " - setup" "\n") (insert " - test" "\n")) (eask-start (let* ((url "https://raw.githubusercontent.com/emacs-eask/template-generate/master/workflow/circle-ci.yml") (dir (expand-file-name ".circleci/")) (basename (or (car (eask-args)) "config.yml")) (filename (expand-file-name basename dir)) (minimum-version (car (cdr (nth 0 eask-depends-on-emacs))))) (ignore-errors (make-directory dir t)) (if (file-exists-p filename) (eask-info "The yaml file already exists `%s`" filename) (eask-with-progress (format " - [1/2] Generating workflow file in %s... " filename) (eask-with-verbosity 'debug (url-copy-file url filename)) "done ✓") (eask-with-progress (format " - [2/2] Configuring workflow file in %s... " filename) (with-current-buffer (find-file filename) ;; Config jobs (when (search-forward "{ EMACS_VERSION }" nil t) (search-backward "{ EMACS_VERSION }" nil t) (delete-region (point) (line-end-position)) (when (version<= minimum-version "26.1") (eask-generate-workflow-circle-ci--insert-jobs "26")) (when (version<= minimum-version "27.1") (insert "\n") (eask-generate-workflow-circle-ci--insert-jobs "27")) (when (version<= minimum-version "28.1") (insert "\n") (eask-generate-workflow-circle-ci--insert-jobs "28")) (when (version<= minimum-version "29.1") (insert "\n") (eask-generate-workflow-circle-ci--insert-jobs "29")) (when (version<= minimum-version "30.1") (insert "\n") (eask-generate-workflow-circle-ci--insert-jobs "30")) (when (version<= minimum-version "31") (insert "\n") (eask-generate-workflow-circle-ci--insert-jobs "master"))) ;; Config matrix (when (search-forward "{ MATRIX_JOBS }" nil t) (search-backward "{ MATRIX_JOBS }" nil t) (delete-region (point) (line-end-position)) (end-of-line) (let ((spaces (spaces-string (current-column)))) (delete-region (line-beginning-position) (line-end-position)) (when (version<= minimum-version "26.3") (insert spaces "- test-ubuntu-emacs-26" "\n")) (when (version<= minimum-version "27.2") (insert spaces "- test-ubuntu-emacs-27" "\n")) (when (version<= minimum-version "28.2") (insert spaces "- test-ubuntu-emacs-28" "\n")) (when (version<= minimum-version "29.4") (insert spaces "- test-ubuntu-emacs-29" "\n")) (when (version<= minimum-version "30.2") (insert spaces "- test-ubuntu-emacs-30" "\n")) (when (version<= minimum-version "31") (insert spaces "- test-ubuntu-emacs-master")))) (save-buffer)) "done ✓") (eask-msg "") (eask-info "✓ Successfully created the yaml file in `%s`" filename)))) ;;; generate/workflow/circle-ci.el ends here ================================================ FILE: lisp/generate/workflow/github.el ================================================ ;;; generate/workflow/github.el --- Generate GitHub Actions workflow yaml file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate GitHub Actions test yaml file, ;; ;; $ eask generate workflow github [file] ;; ;; ;; Positionals: ;; ;; [file] name of the test file; the default is `test.yml` ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let* ((url "https://raw.githubusercontent.com/emacs-eask/template-generate/master/workflow/github.yml") (dir (expand-file-name ".github/workflows/")) (basename (or (car (eask-args)) "test.yml")) (filename (expand-file-name basename dir)) (minimum-version (car (cdr (nth 0 eask-depends-on-emacs))))) (ignore-errors (make-directory dir t)) (if (file-exists-p filename) (eask-info "The yaml file already exists `%s`" filename) (eask-with-progress (format " - [1/2] Generating workflow file in %s... " filename) (eask-with-verbosity 'debug (url-copy-file url filename)) "done ✓") (eask-with-progress (format " - [2/2] Configuring workflow file in %s... " filename) (with-current-buffer (find-file filename) (when (search-forward "{ EMACS_VERSION }" nil t) (search-backward "{ EMACS_VERSION }" nil t) (delete-region (point) (line-end-position)) (end-of-line) (let ((spaces (spaces-string (current-column)))) (delete-region (line-beginning-position) (line-end-position)) (when (version<= minimum-version "26.3") (insert spaces " - 26.3" "\n")) (when (version<= minimum-version "27.2") (insert spaces " - 27.2" "\n")) (when (version<= minimum-version "28.2") (insert spaces " - 28.2" "\n")) (when (version<= minimum-version "29.4") (insert spaces " - 29.4" "\n")) (when (version<= minimum-version "30.2") (insert spaces " - 30.2" "\n")) (when (version<= minimum-version "31") (insert spaces "experimental: [false]" "\n") (insert spaces "include:" "\n") (insert spaces "- os: ubuntu-latest" "\n") (insert spaces " emacs-version: snapshot" "\n") (insert spaces " experimental: true" "\n") (insert spaces "- os: macos-latest" "\n") (insert spaces " emacs-version: snapshot" "\n") (insert spaces " experimental: true" "\n") (insert spaces "- os: windows-latest" "\n") (insert spaces " emacs-version: snapshot" "\n") (insert spaces " experimental: true") (goto-char (point-min)) (when (search-forward "runs-on:" nil t) (end-of-line) (insert "\n continue-on-error: ${{ matrix.experimental }}"))))) (save-buffer)) "done ✓") (eask-msg "") (eask-info "✓ Successfully created the yaml file in `%s`" filename)))) ;;; generate/workflow/github.el ends here ================================================ FILE: lisp/generate/workflow/gitlab.el ================================================ ;;; generate/workflow/gitlab.el --- Generate GitLab Runner workflow yaml file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate GitLab Runner test yaml file, ;; ;; $ eask generate workflow gitlab [file] ;; ;; ;; Positionals: ;; ;; [file] name of the test file; the default is `.gitlab-ci.yml` ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-generate-workflow-gitlab--insert-jobs (version) "Insert GitLab Runner's jobs instruction for specific Emacs' VERSION." (insert "test-" version ":" "\n") (insert " image: silex/emacs:" version "-ci" "\n") (insert " script:" "\n") (insert " - eask clean all" "\n") (insert " - eask package" "\n") (insert " - eask install" "\n") (insert " - eask compile" "\n") (insert "\n")) (eask-start (let* ((url "https://raw.githubusercontent.com/emacs-eask/template-generate/master/workflow/gitlab.yml") (basename (or (car (eask-args)) ".gitlab-ci.yml")) (filename (expand-file-name basename)) (minimum-version (car (cdr (nth 0 eask-depends-on-emacs))))) (if (file-exists-p filename) (eask-info "The yaml file already exists `%s`" filename) (eask-with-progress (format " - [1/2] Generating workflow file in %s... " filename) (eask-with-verbosity 'debug (url-copy-file url filename)) "done ✓") (eask-with-progress (format " - [2/2] Configuring workflow file in %s... " filename) (with-current-buffer (find-file filename) (when (search-forward "{ EMACS_VERSION }" nil t) (search-backward "{ EMACS_VERSION }" nil t) (delete-region (point) (line-end-position)) (when (version<= minimum-version "26.3") (eask-generate-workflow-gitlab--insert-jobs "26.3")) (when (version<= minimum-version "27.2") (eask-generate-workflow-gitlab--insert-jobs "27.2")) (when (version<= minimum-version "28.2") (eask-generate-workflow-gitlab--insert-jobs "28.2")) (when (version<= minimum-version "29.4") (eask-generate-workflow-gitlab--insert-jobs "29.4")) (when (version<= minimum-version "30.2") (eask-generate-workflow-gitlab--insert-jobs "30.2")) (when (version<= minimum-version "31") ;; TODO: snapshot? )) (delete-trailing-whitespace) (save-buffer)) "done ✓") (eask-msg "") (eask-info "✓ Successfully created the yaml file in `%s`" filename)))) ;;; generate/workflow/gitlab.el ends here ================================================ FILE: lisp/generate/workflow/travis-ci.el ================================================ ;;; generate/workflow/travis-ci.el --- Generate Travis CI workflow yaml file -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use generate Travis CI test yaml file, ;; ;; $ eask generate workflow travis-ci [file] ;; ;; ;; Positionals: ;; ;; [file] name of the test file; the default is `.travis.yml` ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (let* ((url "https://raw.githubusercontent.com/emacs-eask/template-generate/master/workflow/travis-ci.yml") (basename (or (car (eask-args)) ".travis.yml")) (filename (expand-file-name basename)) (minimum-version (car (cdr (nth 0 eask-depends-on-emacs))))) (if (file-exists-p filename) (eask-info "The yaml file already exists `%s`" filename) (eask-with-progress (format " - [1/2] Generating workflow file in %s... " filename) (eask-with-verbosity 'debug (url-copy-file url filename)) "done ✓") (eask-with-progress (format " - [2/2] Configuring workflow file in %s... " filename) (with-current-buffer (find-file filename) (when (search-forward "{ EMACS_VERSION }" nil t) (search-backward "{ EMACS_VERSION }" nil t) (delete-region (point) (line-end-position)) (end-of-line) (let ((spaces (spaces-string (current-column)))) (delete-region (line-beginning-position) (line-end-position)) (when (version<= minimum-version "26.3") (insert spaces "- EMACS_CI=emacs-26-3" "\n")) (when (version<= minimum-version "27.2") (insert spaces "- EMACS_CI=emacs-27-2" "\n")) (when (version<= minimum-version "28.2") (insert spaces "- EMACS_CI=emacs-28-2" "\n")) (when (version<= minimum-version "29.4") (insert spaces "- EMACS_CI=emacs-29-4" "\n")) (when (version<= minimum-version "30.2") (insert spaces "- EMACS_CI=emacs-30-2" "\n")) (when (version<= minimum-version "31") (insert spaces "- EMACS_CI=emacs-snapshot")))) (save-buffer)) "done ✓") (eask-msg "") (eask-info "✓ Successfully created the yaml file in `%s`" filename)))) ;;; generate/workflow/travis-ci.el ends here ================================================ FILE: lisp/help/core/analyze ================================================ 💡 You need to specify file(s) you want Eask checker to check $ eask analyze FILE-1 FILE-2 ================================================ FILE: lisp/help/core/bump ================================================ 💡 You need to specify version level(s) in order to make this command work: $ eask bump major 💡 The `bump` command accepts multiple values: $ eask bump major minor patch ================================================ FILE: lisp/help/core/compile ================================================ 💡 You need to specify file(s) you want to compile $ eask compile FILE-1 FILE-2 💡 Or edit Eask file with [files] specifier [+] (files \"FILE-1\" \"FILE-2\") ================================================ FILE: lisp/help/core/concat ================================================ 💡 You need to specify the file [names..] in order to concatenate! $ eask concat [names..] ================================================ FILE: lisp/help/core/docs ================================================ 💡 You need to specify the file [names..] in order to build documentation! $ eask docs [names..] ================================================ FILE: lisp/help/core/eval ================================================ 💡 You need to specify the [form] to evaluate! $ eask eval [form] ================================================ FILE: lisp/help/core/exec ================================================ 💡 You need to specify the program name to execute! $ eask exec [options..] ================================================ FILE: lisp/help/core/info ================================================ 💡 You need an Eask-file to show information about the config/package; to create one: $ eask init Or you can generate an elisp project by doing: $ eask create [project-name] ================================================ FILE: lisp/help/core/init ================================================ 💡 You need to specify an Eask-file in your workspace; to create one use $ eask init 💡 Or you can execute the command with the -g (--global) option $ eask [COMMAND] -g ================================================ FILE: lisp/help/core/install ================================================ 💡 Make sure you have specified a (package-file ..) inside your Eask file! [+] (package-file "PKG-MAIN.el") ================================================ FILE: lisp/help/core/install-deps ================================================ 💡 You can add dependencies by using the specifier [depends-on] [+] (depends-on "PKG-SPEC") ================================================ FILE: lisp/help/core/install-file ================================================ 💡 You can add dependencies by using the specifier [depends-on] [+] (depends-on "PACKAGE-NAME" :file "/path/to/PACKAGE-NAME") ================================================ FILE: lisp/help/core/install-vc ================================================ 💡 You can add dependencies by using the specifier [depends-on] [+] (depends-on "PACKAGE-NAME" :vc "/url/to/PACKAGE-NAME") ================================================ FILE: lisp/help/core/recipe ================================================ 💡 Please specify URL in your main package file: [+] ;; URL: https://example.com/your/repo/url Visit https://github.com/melpa/melpa#recipe-format for more information ⛔ If you have a `multiple-packages-in-one-repository' condition; it will only work on the selected package file. ================================================ FILE: lisp/help/core/reinstall ================================================ 💡 Make sure you have specified a (package-file ..) directive inside your Eask file! [+] (package-file "PKG-MAIN.el") 💡 Or specify package names as arguments $ eask reinstall PKG-1 PKG-2 💡 You first need to install packages before reinstalling! Try the command: $ eask install ================================================ FILE: lisp/help/core/search ================================================ 💡 You need to specify queries to search for $ eask search QUERY-1 QUERY-2 .. ================================================ FILE: lisp/help/core/uninstall ================================================ 💡 Make sure you have specified a (package-file ..) inside your Eask file! [+] (package-file "PKG-MAIN.el") 💡 Or specify package names as arguments $ eask uninstall PKG-1 PKG-2 ================================================ FILE: lisp/help/format/elfmt ================================================ 💡 You need to specify file(s) you want elfmt to format $ eask format elfmt FILE-1 FILE-2 💡 Or edit the Eask-file with [package-file] or [files] specifier [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/format/elisp-autofmt ================================================ 💡 You need to specify file(s) you want elisp-autofmt to format $ eask format elisp-autofmt FILE-1 FILE-2 💡 Or edit the Eask-file with [package-file] or [files] specifier [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/init/cask ================================================ 💡 Make sure you have a valid Cask-file in your directory 💡 Or specify Cask-file explicitly, like: $ eask init --from=cask /path/to/Cask ================================================ FILE: lisp/help/init/eldev ================================================ 💡 Make sure you have a valid Eldev-file in your directory 💡 Or specify Eldev-file explicitly, like: $ eask init --from=eldev /path/to/Eldev ================================================ FILE: lisp/help/init/keg ================================================ 💡 Make sure you have a valid Keg-file in your directory 💡 Or specify Keg-file explicitly, like: $ eask init --from=keg /path/to/Keg ================================================ FILE: lisp/help/init/source ================================================ 💡 Make sure you have a valid elisp file in your directory 💡 Or specify elisp file explicitly, like: $ eask init --from=source /path/to/source.el ================================================ FILE: lisp/help/link/add/error ================================================ 💡 You need the `-pkg.el` file so that `package.el` can recognize your package. Options are, - [ ] Use `eask generate pkg-file` to create one - [ ] Write your own `-pkg.el` file ================================================ FILE: lisp/help/link/add/success/eask ================================================ ✓ You have now created the link, here are things you might want to consider: - [ ] (Optional) Compile source package with `.elc` files If the source package uses Eask, you can use `eask` commands to accomplish these tasks. - `eask compile` ================================================ FILE: lisp/help/link/add/success/pkg ================================================ ✓ The link has been created. Here are some important considerations: - [ ] (Required) `SOURCE-autoloads.el` Prepare autoloads - [ ] (Required) `SOURCE-pkg.el` Prepare -pkg file - [ ] (Optional) Compile source package with `.elc` files If the source package uses Eask, you can use `eask` commands to accomplish these tasks. - `eask generate autoloads` - `eask generate pkg-file` - `eask compile` ================================================ FILE: lisp/help/link/delete ================================================ 💡 You need to specify package(s) you want to unlink $ eask link delete PKG-1 PKG-2 ================================================ FILE: lisp/help/lint/checkdoc ================================================ 💡 You need to specify file(s) you want checkdoc to run $ eask lint checkdoc FILE-1 FILE-2 💡 Or edit the Eask-file with [package-file] or [files] specifier [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/declare ================================================ 💡 You need to specify file(s) you want check-declare to check $ eask lint declare FILE-1 FILE-2 💡 Or edit the Eask-file with [package-file] or [files] specifier [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/elint ================================================ 💡 You need to specify file(s) you want elint to check [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/elisp-lint ================================================ 💡 You need to specify file(s) you want elisp-lint to check [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/elsa ================================================ 💡 You need to specify file(s) you want elsa to check [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/indent ================================================ 💡 You need to specify file(s) you want indent-lint to check [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/keywords ================================================ 💡 Try the following command to see all available keywords, $ eask keywords ================================================ FILE: lisp/help/lint/keywords-file ================================================ 💡 You need to specify package-file in your Eask-file, consider adding the following: [+] (package-file "ENTRY") ================================================ FILE: lisp/help/lint/keywords-header ================================================ 💡 You need to specify keywords header in your package file, consider adding the following: [+] ;; Keywords: "..." ================================================ FILE: lisp/help/lint/org ================================================ 💡 You need to specify pattern(s) you want to check $ eask lint org [FILES..] For example, $ eask lint org README.org docs/*.org ================================================ FILE: lisp/help/lint/package ================================================ 💡 You need to specify file(s) you want package-lint to check [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/lint/regexps ================================================ 💡 You need to specify file(s) you want relint to check [+] (package-file "ENTRY") ; One argument with a string [+] (files "FILE-1" "FILE-2" ...) ; All arguments are wildcard patterns For example, [+] (files "*.el") 💡 Tip: You can use the command [files] to show all selected files $ eask files ================================================ FILE: lisp/help/run/command ================================================ 💡 Make sure you have specified a (eask-defcommand ..) function inside your Eask file! [+] (eask-defcommand my-command [+] "This is a test command." [+] (message "My test commnad!")) ================================================ FILE: lisp/help/run/script ================================================ 💡 Make sure you have a (script ..) directive inside your Eask file! [+] (script "test" "echo This is a test command!") ================================================ FILE: lisp/help/source/add ================================================ 💡 The given source name is not found in our database. You have the following options: - [ ] Pass in one extra argument to specify an `[url]` - [ ] Edit variable `eask-source-mapping` and create a pull request to https://github.com/emacs-eask/cli ================================================ FILE: lisp/help/source/delete ================================================ 💡 The given source name is not found in your Eask-file. List all the archives with: $ eask source list ================================================ FILE: lisp/help/test/ert ================================================ 💡 You need to specify the file(s) you want ert to run: $ eask test ert FILE-1 FILE-2 ================================================ FILE: lisp/help/test/melpazoid ================================================ 💡 You need to specify the directory(ies) you want melpazoid to run: $ eask test melpazoid DIR-1 DIR2 ================================================ FILE: lisp/init/cask.el ================================================ ;;; init/cask.el --- Initialize Eask from Cask -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to convert Cask-file to Eask-file ;; ;; $ eask init --from cask ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function ansi-green "ext:ansi.el") (declare-function ansi-yellow "ext:ansi.el") (declare-function ansi-white "ext:ansi.el") (declare-function -flatten "ext:dash.el") (declare-function cask--read "ext:cask.el") ;; ;;; Core (defvar eask--cask-contents nil "Store Cask-file contents.") (defun eask--cask-filter-contents (name &optional contents) "Filter directives by NAME. Optional argument CONTENTS is used for nested directives. e.g. development." (cl-remove-if-not (lambda (prop) (eq (car prop) name)) (or contents eask--cask-contents))) (defun eask--cask-package-name () "Return package name from Cask-file." (nth 0 (alist-get 'package eask--cask-contents))) (defun eask--cask-package-version () "Return package version from Cask-file." (nth 1 (alist-get 'package eask--cask-contents))) (defun eask--cask-package-description () "Return package description from Cask-file." (nth 2 (alist-get 'package eask--cask-contents))) (defun eask--cask-package-file () "Return package file from Cask-file." (car (alist-get 'package-file eask--cask-contents))) (defun eask--cask-files () "Return files from Cask-file." (alist-get 'files eask--cask-contents)) (defun eask--cask-package-descriptor () "Return package descriptor from Cask-file." (car (alist-get 'package-descriptor eask--cask-contents))) (defun eask--cask-sources () "Return sources from Cask-file." (eask--cask-filter-contents 'source)) (defun eask--cask-reqs () "Return dependencies from Cask-file." (eask--cask-filter-contents 'depends-on)) (defun eask--cask-reqs-dev () "Return development dependencies file from Cask-file." (let ((dev-scopes (eask--cask-filter-contents 'development)) (deps)) (dolist (dev-scope dev-scopes) (setq deps (append deps (eask--cask-filter-contents 'depends-on (cdr dev-scope))))) deps)) (defun eask--cask-remove-emacs-dep (list) "Remove dependency Emacs from dependencies LIST." (cl-remove-if (lambda (req) (when (string= (cadr req) "emacs") (caddr req))) list)) (defun eask--cask-reqs-no-emacs () "Return dependencies from Cask-file but exclude Emacs." (eask--cask-remove-emacs-dep (eask--cask-reqs))) (defun eask--cask-reqs-dev-no-emacs () "Return development dependencies from Cask-file but exclude Emacs." (eask--cask-remove-emacs-dep (eask--cask-reqs-dev))) (defun eask--cask-emacs-version () "Return Emacs version from Cask-file." (let ((reqs (eask--cask-reqs))) (cl-some (lambda (req) (when (string= (cadr req) "emacs") (caddr req))) reqs))) (defun eask--convert-cask (filename) "Convert Cask FILENAME to Eask." (let* ((filename (expand-file-name filename)) (file (file-name-nondirectory (eask-root-del filename))) (new-file (eask-s-replace "Cask" "Eask" file)) (new-filename (expand-file-name new-file)) (eask--cask-contents (ignore-errors (cask--read filename))) ; Read it! (converted)) (eask-with-progress (format "Converting file `%s` to `%s`... " file new-file) (eask-with-verbosity 'debug (cond ((not (string-prefix-p "Cask" file)) (eask-debug "✗ Invalid Cask filename, the file should start with `Cask`")) (t (with-current-buffer (find-file new-filename) (erase-buffer) (goto-char (point-min)) ;; XXX: Newline to look nicer! (eask--unsilent (eask-msg "\n")) (let* ((project-name (or (eask--cask-package-name) (file-name-nondirectory (directory-file-name default-directory)))) (package-name (or (eask--cask-package-name) (eask-read-string (format "package name: (%s) " project-name) nil nil project-name))) (version (or (eask--cask-package-version) (eask-read-string "version: (1.0.0) " nil nil "1.0.0"))) (description (or (eask--cask-package-description) (eask-read-string "description: "))) (guess-entry-point (eask-guess-entry-point project-name)) (entry-point (or (eask--cask-package-file) (eask-read-string (format "entry point: (%s) " guess-entry-point) nil nil guess-entry-point))) (emacs-version (or (eask--cask-emacs-version) (eask-read-string "emacs version: (26.1) " nil nil "26.1"))) (website (eask-read-string "website: ")) (keywords (eask-read-string "keywords: ")) (keywords (split-string keywords "[, ]")) (keywords (string-join keywords "\" \"")) (content (format ";; -*- mode: eask; lexical-binding: t -*- (package \"%s\" \"%s\" \"%s\") (website-url \"%s\") (keywords \"%s\") (package-file \"%s\") " package-name version description website keywords entry-point))) (insert content) (when-let* ((files (eask--cask-files)) (files (if (stringp files) files (-flatten files)))) (insert "(files") (dolist (file files) (if (stringp file) (insert "\n \"" file "\"") (insert "\n " (eask-2str file)))) (insert ")\n")) (when-let* ((pkg-desc (eask--cask-package-descriptor))) (insert "\n") (insert "(package-descriptor \"" (eask-2str pkg-desc) "\")\n")) (insert "\n") (insert "(script \"test\" \"echo \\\"Error: no test specified\\\" && exit 1\")\n") (when-let* ((sources (eask--cask-sources))) (insert "\n") (dolist (source sources) (insert "(source '" (eask-2str (cadr source)) ")\n"))) (insert "\n") (insert "(depends-on \"emacs\" \"" emacs-version "\")") (unless (eask--cask-reqs-no-emacs) (insert "\n")) ; Make sure end line exists! (when-let* ((pkgs (eask--cask-reqs-no-emacs))) (insert "\n") (dolist (pkg pkgs) (let ((val (mapconcat #'eask-2str (cdr pkg) "\" \""))) (insert "(depends-on \"" val "\")\n")))) (when-let* ((pkgs (eask--cask-reqs-dev-no-emacs))) (insert "\n") (insert "(development\n") (dolist (pkg pkgs) (let ((val (mapconcat #'eask-2str (cdr pkg) "\" \""))) (insert " (depends-on \"" val "\")\n"))) (insert " )\n"))) (save-buffer)) (setq converted t)))) (if converted "done ✓" "skipped ✗")) converted)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa" "jcs-elpa") 'package-build 'cask) ;; Start Converting (require 'cask) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (directory-files default-directory t "Cask"))) (converted 0)) (cond ;; Files found, do the action! (files (dolist (file files) (when (eask--convert-cask file) (cl-incf converted))) (eask-msg "") (eask-info "(Total of %s Cask-file%s converted)" converted (eask--sinr converted "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No Cask-files have been converted to Eask)") (eask-help "init/cask"))))) ;;; init/cask.el ends here ================================================ FILE: lisp/init/eldev.el ================================================ ;;; init/eldev.el --- Initialize Eask from Eldev -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to convert Eldev-file to Eask-file ;; ;; $ eask init --from eldev ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-init-eldev--map-elpa (name) "Convert Eldev mapping to Eask mapping." (pcase name ("melpa-unstable" 'melpa) (_ name))) (defun eask-init-eldev--convert (filename) "Convert Eldev FILENAME to Eask." (let* ((filename (expand-file-name filename)) (file (file-name-nondirectory (eask-root-del filename))) (new-file (eask-s-replace "Eldev" "Eask" file)) (new-filename (expand-file-name new-file)) (converted)) (eask-with-progress (format "Converting file `%s` to `%s`... " file new-file) (eask-with-verbosity 'debug (cond ((not (string-prefix-p "Eldev" file)) (eask-debug "✗ Invalid Eldev filename, the file should start with `Eldev`")) (t (with-current-buffer (find-file new-filename) (erase-buffer) (goto-char (point-min)) (let* ((project-name (file-name-nondirectory (directory-file-name default-directory))) (package-name (eask-read-string (format "\npackage name: (%s) " project-name) nil nil project-name)) (version (eask-read-string "version: (1.0.0) " nil nil "1.0.0")) (description (eask-read-string "description: ")) (guess-entry-point (eask-guess-entry-point project-name)) (entry-point (eask-read-string (format "entry point: (%s) " guess-entry-point) nil nil guess-entry-point)) (emacs-version (eask-read-string "emacs version: (26.1) " nil nil "26.1")) (website (eask-read-string "website: ")) (keywords (eask-read-string "keywords: ")) (keywords (split-string keywords "[, ]")) (keywords (string-join keywords "\" \"")) (content (format ";; -*- mode: eask; lexical-binding: t -*- (package \"%s\" \"%s\" \"%s\") (website-url \"%s\") (keywords \"%s\") (package-file \"%s\") (script \"test\" \"echo \\\"Error: no test specified\\\" && exit 1\") " package-name version description website keywords entry-point))) (insert content) (when-let* ((names (mapcar #'car package-archives)) (sources (mapcar #'eask-init-eldev--map-elpa names))) (insert "\n") (dolist (source sources) (insert "(source '" (eask-2str source) ")\n")))) (save-buffer)) (setq converted t)))) (if converted "done ✓" "skipped ✗")) converted)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'eldev) ;; Start Converting (require 'eldev) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (directory-files default-directory t "Eldev"))) (files (cl-remove-if-not (lambda (file) (string= "Eldev" (file-name-nondirectory file))) files)) (converted 0)) (cond ;; Files found, do the action! (files (eldev--set-up) ; XXX: Load once! (dolist (file files) (when (eask-init-eldev--convert file) (cl-incf converted))) (eask-msg "") (eask-info "(Total of %s Eldev-file%s converted)" converted (eask--sinr converted "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No Eldev-files have been converted to Eask)") (eask-help "init/eldev"))))) ;;; init/eldev.el ends here ================================================ FILE: lisp/init/keg.el ================================================ ;;; init/keg.el --- Initialize Eask from Keg -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to convert Keg-file to Eask-file ;; ;; $ eask init --from keg ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; Copied from `keg.el' (defun eask-init-keg--file-read (path) "Return sexp from Keg file (PATH) search from `deafult-directory'. If no found the Keg file, returns nil." (let (sources devs packages lint-disables scripts) (when path (dolist (elm (read (with-temp-buffer (insert-file-contents path) (format "(%s)" (buffer-string))))) (let ((op (car elm)) (args (cdr elm))) (cond ((eq 'source op) (dolist (elm args) (push elm sources))) ((eq 'dev-dependency op) (dolist (elm args) (push elm devs))) ((eq 'package op) (dolist (elm args) (push elm packages))) ((eq 'disable-lint op) (dolist (elm args) (push elm lint-disables))) ((eq 'script op) (dolist (elm args) (push elm scripts)))))) `((sources . ,(nreverse (delete-dups sources))) (devs . ,(nreverse (delete-dups devs))) (packages . ,(nreverse (delete-dups packages))) (disables . ,(nreverse (delete-dups lint-disables))) (scripts . ,(nreverse (delete-dups scripts))))))) (defun eask-init-key--convert (filename) "Convert Keg FILENAME to Eask." (let* ((filename (expand-file-name filename)) (file (file-name-nondirectory (eask-root-del filename))) (new-file (eask-s-replace "Keg" "Eask" file)) (new-filename (expand-file-name new-file)) (contents (ignore-errors (eask-init-keg--file-read filename))) ; Read it! (converted)) (eask-with-progress (format "Converting file `%s` to `%s`... " file new-file) (eask-with-verbosity 'debug (cond ((not (string-prefix-p "Keg" file)) (eask-debug "✗ Invalid Keg filename, the file should start with `Keg`")) (t (with-current-buffer (find-file new-filename) (erase-buffer) (goto-char (point-min)) (let* ((project-name (file-name-nondirectory (directory-file-name default-directory))) (package-name (eask-read-string (format "\npackage name: (%s) " project-name) nil nil project-name)) (version (eask-read-string "version: (1.0.0) " nil nil "1.0.0")) (description (eask-read-string "description: ")) (guess-entry-point (eask-guess-entry-point project-name)) (entry-point (eask-read-string (format "entry point: (%s) " guess-entry-point) nil nil guess-entry-point)) (emacs-version (eask-read-string "emacs version: (26.1) " nil nil "26.1")) (website (eask-read-string "website: ")) (keywords (eask-read-string "keywords: ")) (keywords (split-string keywords "[, ]")) (keywords (string-join keywords "\" \"")) (content (format ";; -*- mode: eask; lexical-binding: t -*- (package \"%s\" \"%s\" \"%s\") (website-url \"%s\") (keywords \"%s\") (package-file \"%s\") (script \"test\" \"echo \\\"Error: no test specified\\\" && exit 1\") " package-name version description website keywords entry-point))) (insert content) (when-let* ((scripts (alist-get 'scripts contents))) (dolist (script scripts) (let* ((cmds (cadr script)) (_ (pop cmds)) (cmds (mapconcat #'identity cmds " "))) (insert "(script \"" (eask-2str (car script)) "\" " (prin1-to-string cmds) ")\n")))) (when-let* ((sources (alist-get 'sources contents))) (insert "\n") (dolist (source sources) (insert "(source '" (eask-2str source) ")\n"))) (insert "\n") (insert "(depends-on \"emacs\" \"" emacs-version "\")")) (unless (alist-get 'packages contents) (insert "\n")) ; Make sure end line exists! (when-let* ((pkgs (alist-get 'packages contents))) (insert "\n") (dolist (pkg pkgs) (insert "(depends-on \"" (eask-2str (car pkg)) "\")\n"))) (when-let* ((devs (alist-get 'devs contents))) (insert "\n") (insert "(development\n") (dolist (dev devs) (insert " (depends-on \"" (eask-2str dev) "\")\n")) (insert " )\n")) (save-buffer)) (setq converted t)))) (if converted "done ✓" "skipped ✗")) converted)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'keg) ;; Start Converting (require 'keg) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (directory-files default-directory t "Keg"))) (files (cl-remove-if-not (lambda (file) (string= "Keg" (file-name-nondirectory file))) files)) (converted 0)) (cond ;; Files found, do the action! (files (dolist (file files) (when (eask-init-key--convert file) (cl-incf converted))) (eask-msg "") (eask-info "(Total of %s Keg-file%s converted)" converted (eask--sinr converted "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No Keg-files have been converted to Eask)") (eask-help "init/keg"))))) ;;; init/keg.el ends here ================================================ FILE: lisp/init/source.el ================================================ ;;; init/source.el --- Initialize Eask from elisp source -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to convert elisp-file to Eask-file ;; ;; $ eask init --from source ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-init-source--convert (filename) "Convert elisp source FILENAME to Eask." (let* ((filename (expand-file-name filename)) (file (file-name-nondirectory (eask-root-del filename))) (new-file (concat "Eask." (file-name-sans-extension file))) (new-filename (expand-file-name new-file)) (pkg-desc (with-temp-buffer (insert-file-contents filename) (eask-ignore-errors-silent (package-buffer-info)))) ; Read it! (converted)) (eask-with-progress (format "Converting file `%s` to `%s`... " file new-file) (eask-with-verbosity 'debug (cond ((not (string-suffix-p ".el" file)) (eask-debug "✗ Invalid elisp filename, the file should end with `.el`")) (t (when pkg-desc (with-current-buffer (find-file new-filename) (erase-buffer) (goto-char (point-min)) (let* ((eask-package-desc pkg-desc) (package-name (package-desc-name pkg-desc)) (version (package-desc-version pkg-desc)) (version (package-version-join version)) (description (package-desc-summary pkg-desc)) (description (prin1-to-string description)) (website (or (eask-package-desc-url) "")) (keywords (eask-package-desc-keywords)) (keywords (string-join keywords "\" \"")) (reqs (package-desc-reqs pkg-desc)) (content (format ";; -*- mode: eask; lexical-binding: t -*- (package \"%s\" \"%s\" %s) (website-url \"%s\") (keywords \"%s\") (package-file \"%s\") (script \"test\" \"echo \\\"Error: no test specified\\\" && exit 1\") (source \"gnu\") " package-name version description website keywords file))) (insert content) (dolist (req reqs) (let* ((req-name (car req)) (req-version (cdr req)) (req-version (package-version-join (car req-version)))) (insert (format "(depends-on \"%s\" \"%s\")\n" req-name req-version))))) (save-buffer)) (setq converted t))))) (if converted "done ✓" "skipped ✗")) (when converted new-filename))) (eask-start (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (directory-files default-directory t ".el"))) (files (cl-remove-if-not (lambda (file) (string-suffix-p ".el" (file-name-nondirectory file))) files)) (converted-files)) (cond ;; Files found, do the action! (files (dolist (file files) (when-let* ((new-filename (eask-init-source--convert file))) (push new-filename converted-files))) ;; Automatically rename file into Eask file when only one file is converted! (when (= (length converted-files) 1) (rename-file (car converted-files) "Eask" t)) (eask-msg "") (eask-info "(Total of %s elisp file%s converted)" (length converted-files) (eask--sinr converted-files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No elisp files have been converted to Eask)") (eask-help "init/source"))))) ;;; init/source.el ends here ================================================ FILE: lisp/link/add.el ================================================ ;;; link/add.el --- Link a local package -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to link a local package ;; ;; $ eask link add ;; ;; ;; Positionals: ;; ;; name of the link ;; location (target package) where you want to link ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "link/list") (eask-load "link/delete") (eask-load "core/install") (defun eask-link-add--package-desc-reqs (desc) "Return a list of requirements from package DESC." (cl-remove-if (lambda (name) (string= name "emacs")) (mapcar #'car (package-desc-reqs desc)))) (defvar eask-link-add--package-name nil "Used to form package name.") (defvar eask-link-add--package-version nil "Used to form package name.") (defun eask-link-add--create (source) "Add link with SOURCE path." (let* ((dir-name (format "%s-%s" eask-link-add--package-name eask-link-add--package-version)) (link-path (expand-file-name dir-name package-user-dir))) (when (file-exists-p link-path) (eask-msg "") (eask-with-progress (ansi-yellow "!! The link is already present; overriding the existing link... ") (eask-link-delete-symlink link-path) (ansi-yellow "done !!"))) (make-symbolic-link source link-path) (eask-msg "") (eask-info "(Created link from `%s` to `%s`)" source (eask-f-filename link-path)))) (eask-start (let* ((names (eask-args)) (name (nth 0 names)) (path (nth 1 names)) (source (expand-file-name path))) (cond ;; Wrong number of arguments. ((<= 3 (length names)) (eask-info "(This command only accepts maximum of 2 arguments: %s)" (mapconcat #'identity names " "))) ;; Source path not found! ((not (file-directory-p source)) (eask-info "(Can't create link %s to non-existing path: %s)" name source)) ;; Create the link (t (let ((links (eask-link-list)) (pkg-el (expand-file-name (package--description-file name) source)) (pkg-eask (car (eask--all-files source))) (pkg-desc)) (cond ((ignore-errors (file-exists-p pkg-el)) (eask-log "💡 Validating package through %s... done!" pkg-el) (with-temp-buffer (insert-file-contents pkg-el) (setq pkg-desc (ignore-errors (if pkg-el (package--read-pkg-desc 'dir) (package-buffer-info))))) (setq eask-link-add--package-name (package-desc-name pkg-desc) eask-link-add--package-version (package-version-join (package-desc-version pkg-desc))) ;; XXX: Install dependencies for linked package (eask-msg "") (eask-install--packages (eask-link-add--package-desc-reqs pkg-desc)) (eask-link-add--create source) (when (and (zerop (length links)) ; if no link previously, (= 1 (length (eask-link-list)))) ; and first link created! (eask-help "link/add/success/pkg" t))) ; don't error ((ignore-errors (file-exists-p pkg-eask)) (eask-log "💡 Validating package through %s... done!" pkg-eask) (eask--save-load-eask-file pkg-eask (progn (setq eask-link-add--package-name (eask-package-name) eask-link-add--package-version (eask-package-version)) ;; XXX: Install dependencies for linked package (eask-msg "") (eask-install-dependencies) ;; Help generates necessary files! (let* ((default-directory source) ; this to make command work! (pkg-file (expand-file-name (concat name "-pkg.el"))) (autoloads-file (expand-file-name (concat name "-autoloads.el"))) (pkg-file-presented (file-exists-p pkg-file)) (autoloads-file-presented (file-exists-p autoloads-file))) (eask-msg "") (eask-with-progress (format " - [1/2] Generating %s file... " autoloads-file) (unless autoloads-file-presented (eask-with-verbosity 'debug (eask-call "generate/autoloads"))) (if autoloads-file-presented "already present ✗" "done ✓")) (eask-with-progress (format " - [2/2] Generating %s file... " pkg-file) (unless pkg-file-presented (eask-with-verbosity 'debug (eask-call "generate/pkg-file"))) (if pkg-file-presented "already present ✗" "done ✓"))) (eask-link-add--create source) (when (and (zerop (length links)) ; if no link previously, (= 1 (length (eask-link-list)))) ; and first link created! (eask-help "link/add/success/eask" t))) ; don't error (eask-error "✗ Error loading Eask-file: %s" pkg-eask))) (t (eask-info "(Missing `%s-pkg.el` file in your source folder)" name) (eask-help "link/add/error")))))))) ;;; link/add.el ends here ================================================ FILE: lisp/link/delete.el ================================================ ;;; link/delete.el --- Delete local linked packages -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to delete local linked packages ;; ;; $ eask link delete [names..] ;; ;; ;; Positionals: ;; ;; [names..] name of the link, accept array ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-load "link/list") (defun eask-link-delete-symlink (path) "Delete symlink PATH." (ignore-errors (delete-file path)) (ignore-errors (delete-directory path t))) (defun eask-link-delete--link (name) "Delete a link by its' NAME." (let* ((links (eask-link-list)) (source (assoc name links)) (link (expand-file-name name package-user-dir))) (if (and source (file-symlink-p link)) (progn (eask-link-delete-symlink link) (eask-info "✓ Package `%s` unlinked" name) t) (eask-info "✗ No linked package name `%s`" name) nil))) (eask-start (let* ((names (eask-args)) (links (eask-link-list)) (link-names (mapcar #'car links)) (deleted 0)) (cond ;; Argument check. ((zerop (length names)) (eask-info "✗ No package to unlink, please specify with the package name") (eask-help "link/delete")) ;; No link to delete ((zerop (length links)) (eask-info "No links present; task ended with no operation")) ;; Link not found ((cl-some (lambda (name) (not (member name link-names))) names) (eask-info "(No operation)") (eask-msg "") (eask-info "Arguments contain invalid link name:") (eask-msg "") (dolist (name names) (unless (memq name link-names) (eask-msg " ✗ %s" name))) (eask-msg "") (eask-call "link/list")) ;; Okay! Good to go! (t (dolist (name names) (when (eask-link-delete--link name) (cl-incf deleted))) (eask-msg "") (eask-info "(Total of %s package%s unlinked, %s skipped)" deleted (eask--sinr deleted "" "s") (- (length names) deleted)))))) ;;; link/delete.el ends here ================================================ FILE: lisp/link/list.el ================================================ ;;; link/list.el --- List all project links -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to list all project links ;; ;; $ eask link list ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-link-list () "Return a list of all links." (mapcar (lambda (file) (cons (eask-f-filename file) (file-truename file))) (cl-remove-if-not #'file-symlink-p (directory-files package-user-dir t)))) (defun eask--print-link (link offset) "Print information regarding the LINK. The argument OFFSET is used to align the result." (eask-println (concat " %-" (eask-2str offset) "s %s") (car link) (cdr link))) (eask-start (if-let* ((links (eask-link-list)) (offset (eask-seq-str-max (mapcar #'car links)))) (progn (eask-info "Available linked packages:") (eask-msg "") (dolist (link links) (eask--print-link link offset)) (eask-info "(Total of %s linked package%s)" (length links) (eask--sinr links "" "s"))) (eask-info "(No linked packages)"))) ;;; link/add.el ends here ================================================ FILE: lisp/lint/checkdoc.el ================================================ ;;; lint/checkdoc.el --- Run checkdoc -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `checkdoc' for all files ;; ;; $ eask lint checkdoc [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want checkdoc to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (defvar checkdoc-version) (defvar checkdoc-create-error-function) (declare-function checkdoc-buffer-label "ext:checkdoc.el") ;; ;;; Core (defvar eask-lint-checkdoc--errors nil "Error flag.") (defun eask-lint-checkdoc--print-error (text start _end &optional _unfixable) "Print error for checkdoc. Arguments TEXT, START, END and UNFIXABLE are required for this function to be assigned to variable `checkdoc-create-error-function'." (setq eask-lint-checkdoc--errors t) (let ((msg (concat (checkdoc-buffer-label) ":" (int-to-string (count-lines (point-min) (or start (point-min)))) ": " text))) (if (eask-strict-p) (error msg) (warn msg)) ;; Return nil because we *are* generating a buffered list of errors. nil)) (setq checkdoc-create-error-function #'eask-lint-checkdoc--print-error) (defun eask-lint-checkdoc--file (filename) "Run checkdoc on FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename)) (eask-lint-checkdoc--errors)) (eask-lint-first-newline) (eask-msg "`%s` with checkdoc (%s)" (ansi-green file) checkdoc-version) (eask-ignore-errors (checkdoc-file filename)) (unless eask-lint-checkdoc--errors (eask-msg "No issues found")))) (eask-start (require 'checkdoc) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (mapcar #'eask-lint-checkdoc--file files) (eask-msg "") (eask-info "(Total of %s file%s %s checked)" (length files) (eask--sinr files "" "s") (eask--sinr files "has" "have"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been linted)") (eask-help "lint/checkdoc"))))) ;;; lint/checkdoc.el ends here ================================================ FILE: lisp/lint/declare.el ================================================ ;;; lint/declare.el --- Run check-declare -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `check-declare' for all files ;; ;; $ eask lint declare [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want check-declare to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (defvar check-declare-warning-buffer) ;; ;;; Core (defun eask-lint-declare--file (filename) "Run check-declare on FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename)) (errors)) (eask-lint-first-newline) (eask-msg "`%s` with check-declare" (ansi-green file)) (setq errors (eask--silent (check-declare-file filename))) (eask-ignore-errors ; Continue checking. (if errors (with-current-buffer check-declare-warning-buffer (eask-report (string-remove-prefix " \n" (buffer-string)))) (eask-msg "No issues found"))))) (eask-start (require 'check-declare) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (mapcar #'eask-lint-declare--file files) (eask-msg "") (eask-info "(Total of %s file%s %s checked)" (length files) (eask--sinr files "" "s") (eask--sinr files "has" "have"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been linted)") (eask-help "lint/declare"))))) ;;; lint/declare.el ends here ================================================ FILE: lisp/lint/elint.el ================================================ ;;; lint/elint.el --- Run elint -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `elint' for all files ;; ;; $ eask lint elint [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want elint to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function elint-get-log-buffer "ext:elsa.el") ;; ;;; Core (defun eask-lint-elint--file (filename) "Run elint on FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename)) (noninteractive)) (eask-lint-first-newline) (eask-msg "`%s` with elint" (ansi-green file)) (eask-with-verbosity 'debug (eask-ignore-errors (elint-file filename))) (let ((log-buffer (elint-get-log-buffer))) (eask-print-log-buffer log-buffer) (kill-buffer log-buffer)))) (eask-start (require 'elint) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (mapcar #'eask-lint-elint--file files) (eask-msg "") (eask-info "(Total of %s file%s %s checked)" (length files) (eask--sinr files "" "s") (eask--sinr files "has" "have"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-msg "") (eask-info "(No files have been linted)") (eask-help "lint/elint"))))) ;;; lint/elint.el ends here ================================================ FILE: lisp/lint/elisp-lint.el ================================================ ;;; lint/elisp-lint.el --- Run elisp-lint -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `elisp-lint' for all files ;; ;; $ eask lint elisp-lint [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want elisp-lint to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function elisp-lint-file "ext:elsa.el") ;; ;;; Core (defconst eask-lint-elisp-lint--version nil "`elisp-lint' version.") (defun eask-lint-elisp-lint--process-file (filename) "Process FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename)) success) (eask-msg "") (eask-msg "`%s` with elisp-lint (%s)" (ansi-green file) eask-lint-elisp-lint--version) (eask-ignore-errors (eask-with-verbosity 'debug (setq success (elisp-lint-file filename))) ;; Report result! (cond (success (eask-msg "No issues found")) ((eask-strict-p) (eask-error "Linting failed")))))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'elisp-lint) (setq eask-lint-elisp-lint--version (eask-package--version-string 'elisp-lint)) ;; Start Linting (require 'elisp-lint) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (mapcar #'eask-lint-elisp-lint--process-file files) (eask-msg "") (eask-info "(Total of %s file%s linted)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-msg "") (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-msg "") (eask-info "(No files have been linted)") (eask-help "lint/elisp-lint"))))) ;;; lint/elisp-lint.el ends here ================================================ FILE: lisp/lint/elsa.el ================================================ ;;; lint/elsa.el --- Run elsa -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `elsa' for all files ;; ;; $ eask lint elsa [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want elsa to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (require 'dash nil t) (defvar elsa-global-state) (declare-function elsa-message-format "ext:elsa.el") (declare-function elsa-analyse-file "ext:elsa.el") (declare-function --each "ext:dash.el") ;; ;;; Core (defconst eask-lint-elsa--version nil "Elsa version.") (defun eask-lint-elsa--analyse-file (filename) "Process FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename)) errors) (eask-msg "") (eask-msg "`%s` with elsa (%s)" (ansi-green file) eask-lint-elsa--version) (eask-ignore-errors (eask-with-verbosity 'debug (setq errors (oref (elsa-analyse-file filename elsa-global-state) errors))) (if errors (--each (reverse errors) (let ((line (string-trim (concat file ":" (elsa-message-format it))))) (cond ((string-match-p "[: ][Ee]rror:" line) (eask-error "%s" line)) ((string-match-p "[: ][Ww]arning:" line) (eask-warn "%s" line)) (t (eask-log "%s" line))))) (eask-msg "No issues found"))))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'elsa) (setq eask-lint-elsa--version (eask-package--version-string 'elsa)) ;; Start Linting (require 'elsa) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (elsa-load-config) (mapcar #'eask-lint-elsa--analyse-file files) (eask-msg "") (eask-info "(Total of %s file%s linted)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-msg "") (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-msg "") (eask-info "(No files have been linted)") (eask-help "lint/elsa"))))) ;;; lint/elsa.el ends here ================================================ FILE: lisp/lint/indent.el ================================================ ;;; lint/indent.el --- Lint the package using `indent-lint' -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to check package with indent-lint, ;; ;; $ eask lint indent [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want indent-lint to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Core (defun eask-lint-indent--undo-pos (entry) "Return the undo pos from ENTRY." (cl-typecase (car entry) (number (car entry)) (string (abs (cdr entry))))) (defun eask-lint-indent--undo-infos (undo-list) "Return list of infos in UNDO-LIST." (let ((infos)) (dolist (elm undo-list) (when-let* ((pos (eask-lint-indent--undo-pos elm)) (line (line-number-at-pos pos)) (expected (progn (eask--goto-line line) (current-indentation)))) (push (list line expected) infos))) infos)) (defun eask-lint-indent--file (file) "Lint indent for FILE." (eask-msg "") (eask-msg "`%s` with indent-lint" (ansi-green (eask-root-del file))) (find-file file) (let ((tick (buffer-modified-tick)) (bs (buffer-string))) (eask-with-temp-buffer (insert bs)) (eask--silent (indent-region (point-min) (point-max))) (if-let* (((/= tick (buffer-modified-tick))) (infos (eask-lint-indent--undo-infos buffer-undo-list))) ;; Indentation changed: warn for each line. (dolist (info infos) (let* ((line (nth 0 info)) (column (nth 1 info)) (current (eask-with-buffer (eask--goto-line line) (current-indentation)))) (eask-ignore-errors (eask-report "%s:%s: Expected indentation is %s but found %s" (buffer-name) line column current)))) (eask-log "No mismatch indentation found")))) (eask-start (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs (eask-args)) (eask-package-el-files)))) ;; XXX: Load all dependencies and elisp files to ensure ;; all macros' indentation is applied. (progn (eask-install-dependencies) (eask-with-verbosity 'debug (ignore-errors (mapc #'load (eask-package-el-files))))) (cond ;; Files found, do the action! (files (mapcar #'eask-lint-indent--file files) (eask-msg "") (eask-info "(Total of %s file%s linted)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No files have been linted)") (eask-help "lint/indent"))))) ;;; lint/indent.el ends here ================================================ FILE: lisp/lint/keywords.el ================================================ ;;; lint/keywords.el --- Lint the package's keywords header -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to check keywords header inside the package, ;; ;; $ eask lint keywords ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Core (require 'finder) (defun eask-lint-keywords--defined (keywords) "Return t if KEYWORDS are defined correctly." (let ((available-keywords (mapcar #'car finder-known-keywords)) (result)) (dolist (keyword keywords) (when (memq (intern keyword) available-keywords) (setq result t))) result)) (eask-start (let ((keywords (eask-package-desc-keywords)) (file (eask-root-del eask-package-file))) (cond ((not eask-package-file) (eask-ignore-errors (eask-report "Can't lint keywords without the package-file specified")) (eask-help "lint/keywords-file")) ((not keywords) (eask-ignore-errors (eask-report "Keywords header seems to be missing in the `%s' file" file)) (eask-help "lint/keywords-header")) (t (eask-lint-first-newline) (eask-msg "`%s` with keywords-lint" (ansi-green file)) (if (eask-lint-keywords--defined keywords) (progn (eask-msg "") (eask-info "(No issues found.)")) (eask-ignore-errors (eask-report "Missing a standard keyword, consider adding one to the Keywords header!")) (eask-help "lint/keywords")))))) ;;; lint/keywords.el ends here ================================================ FILE: lisp/lint/license.el ================================================ ;;; lint/license.el --- Lint the package's license -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to check license for the package, ;; ;; $ eask lint license ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (require 'whitespace nil t) ;; ;;; Core (defun eask-lint-license--s-match-all (contents) "Return t when every CONTENTS match the `buffer-string'." (cl-every (lambda (content) (string-match-p (eask-s-replace "\n" " " content) (buffer-string))) contents)) (defun eask-lint-license--scan (file) "Scan the license FILE." (with-temp-buffer (insert-file-contents file) (let ((whitespace-style '(trailing))) (whitespace-cleanup)) (let ((content (eask-s-replace "\n" " " (buffer-string)))) (erase-buffer) (insert content)) ;; See https://api.github.com/licenses (cond ((eask-lint-license--s-match-all '("Licensed under the Academic Free License version 3.0")) '("afl-3.0" "Academic Free License v3.0" "AFL-3.0")) ((eask-lint-license--s-match-all '("You should have received a copy of the GNU Affero General Public License")) '("agpl-3.0" "GNU Affero General Public License v3.0" "AGPL-3.0")) ((eask-lint-license--s-match-all '("Everyone is permitted to copy and distribute verbatim copies" "but changing it is not allowed." "artistic")) '("artistic-2.0" "Artistic License 2.0" "Artistic-2.0")) ((eask-lint-license--s-match-all '("Licensed under the Apache License, Version 2.0")) '("apache-2.0" "Apache License 2.0" "Apache-2.0")) ((eask-lint-license--s-match-all '("Redistribution and use in source and binary forms" "Redistributions in binary form must reproduce")) '("bsd-2-clause" "BSD 2-Clause \"Simplified\" License" "BSD-2-Clause")) ((eask-lint-license--s-match-all '("Redistribution and use in source and binary forms" "Neither the name of the copyright holder nor")) '("bsd-3-clause" "BSD 3-Clause \"New\" or \"Revised\" License" "BSD-3-Clause")) ((eask-lint-license--s-match-all '("Permission is hereby granted, free of charge, to any person or organization")) '("bsl-1.0" "Boost Software License 1.0" "BSL-1.0")) ((eask-lint-license--s-match-all '("The laws of most jurisdictions throughout the world automatically confer exclusive Copyright")) '("cc0-1.0" "Creative Commons Zero v1.0 Universal" "CC0-1.0")) ((eask-lint-license--s-match-all '("Eclipse Public License - v 2.0" "Eclipse Foundation")) '("epl-2.0" "Eclipse Public License 2.0" "EPL-2.0")) ((eask-lint-license--s-match-all '("Copying and distribution of this file, with or without")) '("fsfap" "FSF All Permissive License" "FSFAP")) ((eask-lint-license--s-match-all '("is free software." "you can redistribute it" "version 2 of the")) '("gpl-2.0" "GNU General Public License v2.0" "GPL-2.0")) ((eask-lint-license--s-match-all '("is free software." "you can redistribute it" "version 3 of the")) '("gpl-3.0" "GNU General Public License v3.0" "GPL-3.0")) ((eask-lint-license--s-match-all '("Permission to use, copy, modify, and/or distribute this")) '("isc" " Internet Systems Consortium" "ISC")) ((eask-lint-license--s-match-all '("Lesser" "GNU" "version 2")) '("lgpl-2.1" "GNU Lesser General Public License v2.1" "LGPL-2.1")) ((eask-lint-license--s-match-all '("This license governs use of the accompanying software." "If you use the software, you accept this license." "If you do not accept the license, do not use the software.")) '("ms-pl" "Microsoft Public License" "MS-PL")) ((eask-lint-license--s-match-all '("Permission is hereby granted, free of charge, to any person")) '("mit" "MIT License" "MIT")) ((eask-lint-license--s-match-all '("http://mozilla.org/MPL/2.0/")) '("mpl-2.0" "Mozilla Public License 2.0" "MPL-2.0")) ((eask-lint-license--s-match-all '("Licensed under the Open Software License version 3.0")) '("osl-3.0" "Open Software License 3.0" "OSL-3.0")) ((eask-lint-license--s-match-all '("This is free and unencumbered software released into")) '("unlicense" "The Unlicense" "Unlicense")) ((eask-lint-license--s-match-all '("Everyone is permitted to copy and distribute verbatim copies")) '("wtfpl-1" "Do What the Fuck You Want To Public License (Version 1)" "WTFPL-1")) ((eask-lint-license--s-match-all '("Everyone is permitted to copy and distribute verbatim or modified")) '("wtfpl-2" "Do What the Fuck You Want To Public License (Version 2)" "WTFPL-2")) ((eask-lint-license--s-match-all '("Permission is granted to anyone to use this software for any purpose,")) '("zlib" "zlib License" "Zlib")) (t '("unknown" "Unknown license" "unknown"))))) (defun eask-lint-license--print-scanned (scanned) "Print all SCANNED license." (eask-msg "available via `eask lint license`") (eask-msg "") (let* ((names (mapcar #'car scanned)) (offset (eask-seq-str-max names)) (fmt (concat " %-" (eask-2str offset) "s %s"))) (dolist (data scanned) (eask-msg fmt (nth 0 data) (nth 3 data))) (eask-msg "") (eask-info "(Total of %s scanned license%s)" (length names) (eask--sinr names "" "s")))) (eask-start (let* ((license-names '("copying" "copying.txt" "license" "license.txt" "license.md" "unlicense")) (files (eask-expand-file-specs license-names)) (scanned)) (cond ((null files) (eask-info "(No license found)")) (t (when (<= 2 (length files)) (eask-warn "!! Multi-licensing detected !!")) (eask-progress-seq " - Sanning" files "done! ✓" (lambda (file) (push (append (eask-lint-license--scan file) `(,file)) scanned))) (eask-msg "") (eask-lint-license--print-scanned (reverse scanned)))))) ;;; lint/license.el ends here ================================================ FILE: lisp/lint/org.el ================================================ ;;; lint/org.el --- Run org-lint on Org files -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `org-lint' for all files ;; ;; $ eask lint org [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want org-lint to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Core (defun eask-lint-org--print-error (file result) "Print the error RESULT from FILE." (let* ((data (cl-second result)) (filename (file-name-nondirectory file)) (line (elt data 0)) (text (elt data 2)) (msg (concat filename ":" line ": " text))) (eask-ignore-errors (eask-report "%s" msg)))) (defun eask-lint-org--file (file) "Run `org-lint' on FILE." (eask-msg "`%s` with org-lint" (ansi-green file)) (with-temp-buffer (insert-file-contents file) (org-mode) (if-let* ((results (org-lint))) (mapc (lambda (result) (eask-lint-org--print-error file result)) results) (eask-msg "No issues found")))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu") 'org) ;; Start Linting (require 'org-lint) (let* ((patterns (eask-args)) (files (eask-expand-file-specs patterns))) (cond ;; Files found, do the action! (files (eask-msg "") (mapcar #'eask-lint-org--file files) (eask-msg "") (eask-info "(Total of %s file%s linted)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-msg "") (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-msg "") (eask-info "(No files have been linted)") (eask-help "lint/org"))))) ;;; lint/org.el ends here ================================================ FILE: lisp/lint/package.el ================================================ ;;; lint/package.el --- Lint the package using `package-lint' -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to lint current Emacs package, ;; ;; $ eask lint package [files..] ;; ;; ;; Positionals: ;; ;; [files..] specify files to do package lint ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function package-lint-current-buffer "ext:package-lint.el") ;; ;;; Handle options (eask-add-hook '( eask-before-command-hook) (when (and (not (boundp 'package-lint-batch-fail-on-warnings)) (not (eask-strict-p))) ;; TODO: This doesn't work since this variable only controls ;; `package-lint' exit code. (setq package-lint-batch-fail-on-warnings nil))) ;; ;;; Core (defconst eask-lint-package--version nil "`package-lint' version.") (defun eask-lint-package--file (filename) "Package lint FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename))) (eask-msg "") (eask-msg "`%s` with package-lint (%s)" (ansi-green file) eask-lint-package--version) (with-current-buffer (find-file filename) (package-lint-current-buffer) (kill-current-buffer))) (eask-print-log-buffer "*Package-Lint*")) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'package-lint) (setq eask-lint-package--version (eask-package--version-string 'package-lint)) ;; Start Linting (require 'package-lint) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (eask-pkg-init) ; XXX: Avoid not installable error! (setq package-lint-main-file eask-package-file) (mapcar #'eask-lint-package--file files) (eask-msg "") (eask-info "(Total of %s file%s linted)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-msg "") (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-msg "") (eask-info "(No files have been linted)") (eask-help "lint/package"))))) ;;; lint/package.el ends here ================================================ FILE: lisp/lint/regexps.el ================================================ ;;; lint/regexps.el --- Lint the package using `relint' -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to run `relint' for all files ;; ;; $ eask lint regexps [files..] ;; ;; ;; Positionals: ;; ;; [files..] files you want relint to run on ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Externals (declare-function relint-buffer "ext:package-lint.el") ;; ;;; Flags (eask-command-check "27.1") ;; ;;; Core (defconst eask-lint-regexps--relint-version nil "`relint' version.") (defun eask-lint-regexps--relint-file (filename) "Package lint FILENAME." (let* ((filename (expand-file-name filename)) (file (eask-root-del filename)) (errors)) (eask-msg "") (eask-msg "`%s` with relint (%s)" (ansi-green file) eask-lint-regexps--relint-version) (with-current-buffer (find-file filename) (setq errors (relint-buffer (current-buffer))) (dolist (err errors) (let* ((msg (seq-elt err 0)) (error-pos (seq-elt err 2)) (severity (seq-elt err 7)) (report-func (pcase severity (`error #'eask-error) (`warning #'eask-warn) (_ #'eask-info)))) (eask-ignore-errors (funcall report-func "%s:%s %s: %s" file (line-number-at-pos error-pos) (capitalize (eask-2str severity)) msg)))) (unless errors (eask-msg "No issues found")) (kill-current-buffer)))) (eask-start ;; Preparation (eask-archive-install-packages '("gnu") 'relint) (setq eask-lint-regexps--relint-version (eask-package--version-string 'relint)) ;; Start Linting (require 'relint) (let* ((patterns (eask-args)) (files (if patterns (eask-expand-file-specs patterns) (eask-package-el-files)))) (cond ;; Files found, do the action! (files (setq package-lint-main-file eask-package-file) (mapcar #'eask-lint-regexps--relint-file files) (eask-msg "") (eask-info "(Total of %s file%s linted)" (length files) (eask--sinr files "" "s"))) ;; Pattern defined, but no file found! (patterns (eask-msg "") (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-msg "") (eask-info "(No files have been linted)") (eask-help "lint/regexps"))))) ;;; lint/regexps.el ends here ================================================ FILE: lisp/run/command.el ================================================ ;;; run/command.el --- Run custom command -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to run custom command ;; ;; $ eask run command [names..] ;; ;; ;; Positionals: ;; ;; [names..] name of the function command ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-run-command--desc (name) "Return command's description by its command's NAME." (car (split-string (or (documentation name) "") "\n"))) (defun eask-run-command--print-commands () "Print all available commands." (eask-msg "available via `eask run command`") (eask-msg "") (let* ((keys (reverse eask-commands)) (offset (eask-seq-str-max keys)) (fmt (concat " %-" (eask-2str offset) "s %s"))) (dolist (key keys) (eask-msg fmt key (eask-run-command--desc key))) (eask-msg "") (eask-info "(Total of %s available script%s)" (length keys) (eask--sinr keys "" "s")))) (defun eask-run-command--execute (name) "Execute the command by NAME." (eask-info "[RUN]: %s" name) (funcall (eask-intern name))) (defun eask-run-command--unmatched-commands (commands) "Return a list of COMMANDS that cannot be found in `eask-commands'." (let (unmatched) (dolist (command commands) (unless (memq (eask-intern command) eask-commands) (push command unmatched))) unmatched)) (eask-start (cond ((null eask-commands) (eask-info "(No command specified)") (eask-help "run/command")) ((eask-all-p) (dolist (name (reverse eask-commands)) (eask-run-command--execute name))) ((when-let* ((commands (eask-args))) (if-let* ((unmatched (eask-run-command--unmatched-commands commands))) (progn ; if there are unmatched commands, don't even try to execute (eask-info "(Missing command%s: `%s`)" (eask--sinr unmatched "" "s") (mapconcat #'identity unmatched ", ")) (eask-msg "") (eask-run-command--print-commands)) (dolist (command commands) (eask-run-command--execute command)) t))) (t (eask-run-command--print-commands)))) ;;; run/command.el ends here ================================================ FILE: lisp/run/script.el ================================================ ;;; run/script.el --- Run the script -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to run scripts, ;; ;; $ eask run script [names..] ;; ;; ;; Positionals: ;; ;; [names..] run the script named ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defconst eask-run-script--file (expand-file-name "run" eask-homedir) "Target file to export the `run' scripts.") (defun eask-run-script--print-scripts () "Print all available scripts." (eask-msg "available via `eask run script`") (eask-msg "") (let* ((keys (mapcar #'car (reverse eask-scripts))) (offset (eask-seq-str-max keys)) (fmt (concat " %-" (eask-2str offset) "s %s"))) (dolist (key keys) (eask-msg fmt key (cdr (assoc key eask-scripts)))) (eask-msg "") (eask-info "(Total of %s available script%s)" (length keys) (eask--sinr keys "" "s")))) (defun eask-run-command--export (command) "Export COMMAND instruction." (ignore-errors (make-directory eask-homedir t)) ; generate dir `~/.eask/' (when eask-is-pkg ;; XXX: Due to `MODULE_NOT_FOUND` not found error from vcpkg, ;; see https://github.com/vercel/pkg/issues/1356. ;; ;; We must split up all commands! (setq command (eask-s-replace " && " "\n" command))) (setq command (concat command " " (eask-rest))) (write-region (concat command "\n") nil eask-run-script--file t)) (defun eask-run-script--unmatched-scripts (scripts) "Return a list of SCRIPTS that cannot be found in `eask-scripts'." (let (unmatched) (dolist (script scripts) (unless (assoc script eask-scripts) (push script unmatched))) unmatched)) (eask-start ;; Preparation (ignore-errors (delete-file eask-run-script--file)) ;; Start the task (cond ((null eask-scripts) (eask-info "(No scripts specified)") (eask-help "run/script")) ((eask-all-p) ; Run all scripts (dolist (data (reverse eask-scripts)) (eask-run-command--export (cdr data)))) ((when-let* ((scripts (eask-args))) (if-let* ((unmatched (eask-run-script--unmatched-scripts scripts))) (progn ; if there are unmatched scripts, don't even try to execute (eask-info "(Missing script%s: `%s`)" (eask--sinr unmatched "" "s") (mapconcat #'identity unmatched ", ")) (eask-msg "") (eask-run-script--print-scripts)) (dolist (script scripts) (let* ((data (assoc script eask-scripts)) (name (car data)) (command (cdr data))) (eask-run-command--export command))) t))) (t (eask-run-script--print-scripts)))) ;;; run/script.el ends here ================================================ FILE: lisp/source/add.el ================================================ ;;; source/add.el --- Add an archive source -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to add an archive source ;; ;; $ eask source add ;; ;; ;; Positionals: ;; ;; name of the archive ;; link to the archive ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-source-add--from-mapping (name url) "Return t if NAME and URL matched our database." (string= (eask-source-url name) url)) (defun eask-source-add (name url exists) "Add an archive source by NAME. If argument URL is nil; ignore the insertion. Arguments EXISTS is used to print the information." (let* ((style-sym (string-match "([ \t\n\r]*source[ \t\n\r]*[']+" (buffer-string))) (name-str (if style-sym (concat "'" name) (concat "\"" name "\""))) (built-in (eask-source-add--from-mapping name url))) (if (and url (not built-in)) (insert "(source " name-str " \"" url "\")\n") (insert "(source " name-str ")\n")) (eask-info "(New source `%s' added and points to `%s')" name (or url (cdr exists))))) (defun eask-source-add--write (name url exists) "Write source construct by NAME and URL. The argument URL can be nil. The argument EXISTS is use to search for correct position to insert new source." (with-current-buffer (find-file eask-file) (goto-char (point-min)) (cond (exists (if (re-search-forward (concat "([ \t\n\r]*source[ \t\n\r]*['\"]+" name) nil t) (let ((start (point)) (built-in (eask-source-add--from-mapping name url))) (when (string= "\"" (string (char-after))) (cl-incf start)) (re-search-forward "[ \t\r\n\")]*" nil t) ; Forward to non-space characters! (forward-char -1) (when (string= "\n" (string (char-after))) (forward-char -1)) (pcase (string (char-after)) (")" (unless built-in (insert " \"" url "\""))) ("\"" (if built-in (delete-region start (save-window-excursion (search-forward ")" nil t) (1- (point)))) (let ((old (thing-at-point 'string)) (new (concat "\"" url "\""))) (delete-region (point) (+ (point) (length old))) (insert new))))) (eask-info "(Changed archive `%s''s location from `%s' to `%s')" name (cdr exists) url)) (goto-char (point-max)) (eask-source-add name url exists))) (t (if (re-search-forward "([ \t\n\r]*source[ \t\n\r]*['\"]+" nil t) (forward-paragraph) (goto-char (point-max))) (eask-source-add name url exists))) (save-buffer))) (defun eask-source-add--ask-if-overwrite (name url) "Ask source overwrite if needed. Arguments NAME and URL are main arguments for this command." (if-let* ((exists (assoc name package-archives)) (old-url (cdr exists))) (cond ((string= old-url url) (eask-info "(Nothing has changed due to the URLs are the same)")) ((yes-or-no-p (format (concat "The archive `%s' is already exists and currently points to `%s'. Do you want to overwrite it? ") name old-url)) (eask-source-add--write name url exists)) (t (eask-info "(Nothing has changed, aborted)"))) (eask-source-add--write name url nil))) (eask-start (let* ((args (eask-args)) (name (nth 0 args)) (url (nth 1 args))) (cond (url (eask-source-add--ask-if-overwrite name url)) (t (if-let* ((archive (assoc (eask-intern name) eask-source-mapping)) (name (car archive)) (name (eask-2str name)) (url (eask-source-url name))) ; Use the URL from our database! (eask-source-add--ask-if-overwrite name url) (eask-info "(Invalid source name, `%s')" name) (eask-help "source/add")))))) ;;; source/add.el ends here ================================================ FILE: lisp/source/delete.el ================================================ ;;; source/delete.el --- Remove an archive source -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to remove an archive source ;; ;; $ eask source delete ;; ;; ;; Positionals: ;; ;; name of the archive ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-source-delete (name) "Delete an archive source by NAME." (with-current-buffer (find-file eask-file) (goto-char (point-min)) (when (re-search-forward (concat "([ \t\n\r]*source[ \t\n\r]*['\"]+" name) nil t) (delete-region (line-beginning-position) (1+ (line-end-position))) (eask-info "(Delete source `%s')" name)) (save-buffer))) (eask-start (let* ((args (eask-args)) (name (nth 0 args))) (if-let* ((exists (assoc name package-archives))) (eask-source-delete name) (eask-info "(Invalid source name, `%s')" name) (eask-help "source/delete")))) ;;; source/delete.el ends here ================================================ FILE: lisp/source/list.el ================================================ ;;; source/list.el --- List all source information -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Commmand use to list all source information ;; ;; $ eask source list ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (eask-call "core/archives")) ;;; source/list.el ends here ================================================ FILE: lisp/test/activate.el ================================================ ;;; test/activate.el --- activate package -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to activate package ;; ;; $ eask test activate ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (eask-pkg-init) (eask-msg "") (let ((name (eask-guess-package-name))) (eask-with-progress (format "Activating the package `%s'... " (ansi-green name)) (require (intern name)) "succeeded ✓")) ;; XXX: Call `core/load' command to load the files for activation tests! ;; The command will parse the argument itself, so we don't have to worry! (eask-call "core/load")) ;;; test/activate.el ends here ================================================ FILE: lisp/test/buttercup.el ================================================ ;;; test/buttercup.el --- Run buttercup tests -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command to run buttercup tests, ;; ;; $ eask test buttercup ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'buttercup) ;; Start Testing (require 'buttercup) ;; Propose fix from https://github.com/jorgenschaefer/emacs-buttercup/pull/217 (let* ((load-path (cons "." load-path)) ;; this does not include options (args (eask-args))) ;; `buttercup-run-discover' uses `command-line-args-left' not `command-line-args' (setq command-line-args-left args) ;; Seems like `buttercup-run-discover' only works on directories that are ;; children of the current directory. When given a parent directory it ;; always fails with "No suites found", even if there are tests. ;; Since this is a bit confusing, we warn the user specifically; ;; see discussion https://github.com/emacs-eask/cli/pull/281. (when-let* ((bad-arg (seq-find (lambda (x) (not (file-in-directory-p x default-directory))) args))) (error "Buttercup cannot run in parent directory: %s" bad-arg)) (buttercup-run-discover))) ;;; test/buttercup.el ends here ================================================ FILE: lisp/test/ecukes.el ================================================ ;;; test/ecukes.el --- Run ecukes tests -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command to run ecukes tests, ;; ;; $ eask test ecukes ;; ;; ;; Positionals: ;; ;; [files..] specify feature files to do ecukes tests ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (defun eask-test-ecukes--run (files) "Run ecukes on FILES. Modified from function `ecukes-cli/run'." (ecukes-load) (ecukes-reporter-use ecukes-cli-reporter) (ecukes-run files)) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'ecukes) ;; Start Testing (require 'ecukes) (let* ((patterns (eask-args)) (files (eask-expand-file-specs patterns))) (cond ;; Files found, do the action! (files (eask-test-ecukes--run files)) ;; Pattern defined, but no file found! (patterns (eask-msg "") (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Run default action. (t (ecukes))))) ;;; test/ecukes.el ends here ================================================ FILE: lisp/test/ert-runner.el ================================================ ;;; test/ert-runner.el --- Run ert tests using ert-runner -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command to run ert tests using ert-runner, ;; ;; $ eask test ert-runner [files..] ;; ;; ;; Positionals: ;; ;; [files..] specify files to run ert tests ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Handle options (eask-add-hook '( eask-before-command-hook) (when (eask-reach-verbosity-p 'debug) (setq ert-runner-verbose t))) ;; ;;; Core (defun eask-test-ert-runner--run (fnc &rest args) "Run around function `ert-runner/run'. Arguments FNC and ARGS are used for advice `:around'. Handle the argument ARGS when command arguments are specified." (let* ((patterns (eask-args)) (files (eask-expand-file-specs patterns))) (setq args files)) (apply fnc args)) (advice-add 'ert-runner/run :around #'eask-test-ert-runner--run) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'ert-runner) ;; Start Testing (require 'ert-runner)) ;;; test/ert-runner.el ends here ================================================ FILE: lisp/test/ert.el ================================================ ;;; test/ert.el --- Run ert tests -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command to run ert tests, ;; ;; $ eask test ert [files..] ;; ;; ;; Positionals: ;; ;; [files..] specify files to run ert tests ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (require 'ert) (defvar eask-test-ert--message-loop nil "Prevent inifinite recursive message function.") (defun eask-test-ert--message (fnc &rest args) "Colorized ert messages. Arguments FNC and ARGS are used for advice `:around'." (if eask-test-ert--message-loop (apply fnc args) (let ((eask-test-ert--message-loop t) (text (ignore-errors (apply #'format args)))) (cond ;; (message nil) is used to clear the minibuffer ;; However, format requires the first argument to be a format string ((null (car args)) (apply fnc args)) ((string-match-p "^[ ]+FAILED " text) (eask-msg (ansi-red text))) ((string-match-p "^[ ]+SKIPPED " text) (eask-msg text)) ((string-match-p "^[ ]+passed " text) (eask-msg (ansi-green text))) (t (apply fnc args)))))) (advice-add 'message :around #'eask-test-ert--message) (eask-start (let* ((patterns (eask-args)) (files (eask-expand-file-specs patterns))) (cond ;; Files found, do the action! (files (eask-pkg-init) (eask-ignore-errors (mapc #'load-file files) (ert-run-tests-batch-and-exit))) ;; Pattern defined, but no file found! (patterns (eask-info "(No files match wildcard: %s)" (mapconcat #'identity patterns " "))) ;; Default, print help! (t (eask-info "(No tests found.)") (eask-help "test/ert"))))) ;;; test/ert.el ends here ================================================ FILE: lisp/test/melpazoid.el ================================================ ;;; test/melpazoid.el --- Run melpazoid tests -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command to run melpazoid tests, ;; ;; $ eask test melpazoid ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) ;; ;;; Core (defcustom eask-test-melpazoid-el-url "https://raw.githubusercontent.com/riscy/melpazoid/master/melpazoid/melpazoid.el" "Url path to melpazoid's elisp file." :type 'string :group 'eask) (eask-start ;; Preparation (eask-archive-install-packages '("gnu" "melpa") 'package-lint 'pkg-info) ;; Start Testing (let* ((dirs (or (eask-args) `(,default-directory)))) (cond ;; Files found, do the action! (dirs (dolist (dir dirs) (let ((default-directory (expand-file-name dir))) (eask-info "[+] %s" default-directory) (eask-ignore-errors (eask-import eask-test-melpazoid-el-url))))) ;; Default, print help! (t (eask-info "(No tests found.)") (eask-help "test/melpazoid"))))) ;;; test/melpazoid.el ends here ================================================ FILE: lisp/util/root.el ================================================ ;;; util/root.el --- Display the effective installation directory -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Command use to display the effective installation directory, ;; ;; $ eask root ;; ;;; Code: (let ((dir (file-name-directory (nth 1 (member "-scriptload" command-line-args))))) (load (expand-file-name "_prepare.el" (locate-dominating-file dir "_prepare.el")) nil t)) (eask-start (eask-println "%s\n" user-emacs-directory)) ;;; util/root.el ends here ================================================ FILE: package.json ================================================ { "name": "@emacs-eask/cli", "version": "0.12.10", "description": "A set of command-line tools to build Emacs packages", "main": "eask.js", "scripts": { "test-unsafe": "cross-env ALLOW_UNSAFE=1 jest", "test-debug": "cross-env DEBUG=1 jest", "test": "jest", "test-reset": "rm -rf ./test/jest/.eask ./test/jest/*/.eask; git restore ./test/jest/*/", "pkg-all": "pkg package.json", "pkg-linux-arm64": "pkg package.json -t node*-linuxstatic-arm64 --output dist/eask --public", "pkg-linux-x64": "pkg package.json -t node*-linuxstatic-x64 --output dist/eask --public", "pkg-macos-arm64": "pkg package.json -t node*-macos-arm64 --output dist/eask --public", "pkg-macos-x64": "pkg package.json -t node*-macos-x64 --output dist/eask --public", "pkg-win-arm64": "pkg package.json -t node*-win-arm64 --output dist/eask --public", "pkg-win-x64": "pkg package.json -t node*-win-x64 --output dist/eask --public" }, "bin": { "eask": "./eask.js" }, "repository": { "type": "git", "url": "https://github.com/emacs-eask/cli.git" }, "keywords": [ "emacs", "package", "management", "cli" ], "author": "Jen-Chieh Shen", "license": "GPL-3.0-or-later", "dependencies": { "which": "^7.0.0", "yargs": "^17.0.0" }, "devDependencies": { "@yao-pkg/pkg": "^6.0.0", "cross-env": "^10.1.0", "jest": "^30.3.0", "semver-compare": "^1.0.0" }, "jest": { "rootDir": "./test/jest", "testTimeout": 120000 }, "files": [ "cmds", "src", "lisp" ], "publishConfig": { "access": "public" }, "pkg": { "scripts": [ "cmds/**/*.js", "src/**/*.js" ], "targets": [ "node16-linux-x64", "node16-macos-x64", "node16-win-x64" ], "outputPath": "dist" } } ================================================ FILE: src/env.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const os = require('os'); const path = require('path'); global.UTIL = require('./util'); global.IS_PKG = path.basename(process.execPath).startsWith('eask'); /* Environment PATH */ global.EASK_HOMEDIR = os.homedir().replace(/\\/g, '/') + '/.eask/'; global.EASK_EMACS = process.env.EMACS || process.env.EASK_EMACS || "emacs"; process.env.EMACS = EASK_EMACS; // Set back the value. /* Titles */ global.TITLE_CMD_OPTION = 'Command Options:'; global.TITLE_PROXY_OPTION = 'Proxy Options:'; /* Exit Code * * This must match with the variable `eask--exit-code` in * the file `lisp/_prepare.el`. */ global.EXIT_SUCCESS = 0; global.EXIT_FAILURE = 1; global.EXIT_MISUSE = 2; /* CI */ global.GITHUB_ACTIONS = (process.env.GITHUB_WORKSPACE !== undefined); ================================================ FILE: src/util.js ================================================ /** * Copyright (C) 2022-2026 the Eask authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; const path = require('path'); const child_process = require("child_process"); /** * Check to see if a program is installed and exists on the path. * @param { String } command - Program name. * @return Return path or null if not found. */ function which(command) { return require('which').sync(command, { nothrow: true }); } /** * Convert Windows backslash paths to slash paths: `foo\\bar` -> `foo/bar` * @see https://github.com/sindresorhus/slash */ function slash(path) { const isExtendedLengthPath = path.startsWith('\\\\?\\'); if (isExtendedLengthPath) { return path; } return path.replace(/\\/g, '/'); } /** * Return string escaped double quotes. * @param { String } str - String to escape string. * @return Escaped string. */ function escape_str(str) { return str.replaceAll('\"', '\\"'); } /** * Return arguments after matching string. */ function take_after(arr, str) { let index = arr.indexOf(str); if (index === -1) return arr; return arr.slice(index + 1); } /** * Return arguments after `--` in list. */ function _rest_args() { let index = process.argv.indexOf('--'); if (index === -1) return []; return process.argv.slice(index + 1); } /** * Form CLI arguments into a single string. * @see https://github.com/emacs-eask/cli/issues/128 * @param { Array } argv - Argument vector. */ function cli_args(argv) { let result = ''; let first = true; argv.forEach(function (element) { // XXX: We wrap double quotes if the string contains spaces if (/\s/g.test(element)) { element = escape_str(element); element = '\"' + element + '\"'; } if (first) result += element; else result += ' ' + element; first = false; }); return result; } /* * Remove `undefined` item from the array * @param { Array } arr - target array */ function _remove_undefined(arr) { return arr.filter(elm => { return elm !== undefined; }); } /* Return plugin directory */ function plugin_dir() { let root = (IS_PKG) ? process.execPath : __dirname; return path.join(root, '..'); } /** * Define flag with proper alias flag. * @param { boolean } arg - argument receive from yargs. * @param { string } name - the flag representation in alias. */ function def_flag(arg, name, val = undefined) { if (arg === undefined) return undefined; if (val === undefined) return ['--eask' + name]; return ['--eask' + name, val]; } /** * Return the invocation program. * @return This would either be a path or paths with newline. */ function _invocation() { if (IS_PKG) return process.execPath; // The is consist of `node` (executable) + `eask` (script) return process.argv[0] + '\n' + process.argv[1]; } /** * Setup the environment variables so Emacs could receive them. */ function setup_env() { /* Home Directory */ process.env.EASK_INVOCATION = _invocation(); process.env.EASK_HOMEDIR = EASK_HOMEDIR; if (IS_PKG) process.env.EASK_IS_PKG = IS_PKG; process.env.EASK_REST_ARGS = _rest_args(); if (GITHUB_ACTIONS) { /* XXX: isTTY flag will always be undefined in GitHub Actions; we will have * explicitly set environment variables. * * See https://github.com/actions/runner/issues/241 */ process.env.EASK_HASCOLORS = 'true'; } else { if (process.stdout.isTTY !== undefined) { if (process.stdout.hasColors()) process.env.EASK_HASCOLORS = 'true'; } } } /** * Handle global options. * * @param { Object } argv - is a parsed object from yargs. */ function _global_options(argv) { let flags = []; /* Boolean type */ flags.push(def_flag(argv.global, '-g')); flags.push(def_flag(argv.config, '-c')); flags.push(def_flag(argv.all, '-a')); flags.push(def_flag(argv.quick, '-q')); flags.push(def_flag(argv.force, '-f')); flags.push(def_flag(argv.debug, '--debug')); flags.push(def_flag(argv.strict, '--strict')); flags.push(def_flag(argv['allow-error'], '--allow-error')); flags.push(def_flag(argv.insecure, '--insecure', argv.insecure)); flags.push(def_flag(argv.timestamps, (argv.timestamps) ? '--timestamps' : '--no-timestamps')); flags.push(def_flag(argv['log-level'], (argv['log-level']) ? '--log-level' : '--no-log-level')); flags.push(def_flag(argv['log-file'], (argv['log-file']) ? '--log-file' : '--no-log-file')); flags.push(def_flag(argv['elapsed-time'], (argv['elapsed-time']) ? '--elapsed-time' : '--no-elapsed-time')); flags.push(def_flag(argv['no-color'], '--no-color')); /* Numeric type */ flags.push(def_flag(argv.verbose, '--verbose', argv.verbose)); /* String type */ flags.push(def_flag(argv.proxy, '--proxy', argv.proxy)); flags.push(def_flag(argv['http-proxy'], '--http-proxy', argv['http-proxy'])); flags.push(def_flag(argv['https-proxy'], '--https-proxy', argv['https-proxy'])); flags.push(def_flag(argv['no-proxy'], '--no-proxy', argv['no-proxy'])); return flags; } /** * Check any invalid arguments or options. * @param { JSON } (argv - Argument vector. * @return Return true if there is no input error. */ function _check_argv(argv) { /* Numeric type */ if (argv.verbose !== undefined && // this must exclude NaN -- yargs default value for numeric type !(argv.verbose >= 0 && argv.verbose <= 5)) { console.warn("invalid value for --verbose option: must be a number between 0 and 5"); return false; } return true; } /** * Form elisp script path. * @param { string } name - Name of the script without extension. */ function el_script(name) { let _script = 'lisp/' + name + '.el'; let _path = path.join(plugin_dir(), _script); return _path; } /** * Get the working environment name. * @param { JSON } argv - Argument vector. * @return Return a string represent the current working environment. */ function _environment_name (argv) { if (argv.global) return 'global (~/)'; else if (argv.config) return 'configuration (~/.emacs.d/)'; else return 'development (./)'; } /** * Call emacs process * @param { string } script - name of the script from `../lisp` * @param { string } args - the rest of the arguments */ async function e_call(argv, script, ...args) { if (!which(EASK_EMACS)) { console.warn("Emacs is not installed (cannot find `" + EASK_EMACS + "' executable)"); return; } if (!_check_argv(argv)) { process.exit(EXIT_FAILURE); return; } return new Promise(resolve => { let _path = el_script(script); let cmd_base = [EASK_EMACS, '-Q', '--script', _path]; let cmd_args = args.flat(); let cmd_global = _global_options(argv).flat(); let cmd = cmd_base.concat(cmd_args).concat(cmd_global); cmd = _remove_undefined(cmd); if (4 <= argv.verbose) { // `debug` scope let env_status = _environment_name(argv); console.log(`Running Eask in the ${env_status} environment`); console.log('Press Ctrl+C to cancel.'); console.log(''); console.log('Executing script inside Emacs...'); console.log(''); } if (5 <= argv.verbose) { // `all` scope console.warn('[EXEC] ' + cmd.join(' ')); console.warn(''); } setup_env(); let proc = child_process.spawn(cli_args(cmd), { stdio: 'inherit', shell: true }); proc.on('close', function (code) { if (code == 0) { resolve(code); return; } process.exit(code); }); }); } /** * Get the command count, not including options. * @return Return a size of the command array. */ function cmd_count() { let args = process.argv.slice(2); args = args.filter(elm => { return !elm.startsWith('-'); }); return args.length; } /** * Hide command unless the option `--show-hidden` is specified. * @param { string | boolean } description - to display when comand is showed. * @param { integer } level - used to compare with command count. * @return Return a string to show command, else we return false. */ function hide_cmd(description, level = 1) { if ((process.argv.includes('--show-hidden')) || level <= cmd_count()) // When display in submenu! return description; return false; } /* * Module Exports */ module.exports.which = which; module.exports.slash = slash; module.exports.escape_str = escape_str; module.exports.take_after = take_after; module.exports.cli_args = cli_args; module.exports.plugin_dir = plugin_dir; module.exports.def_flag = def_flag; module.exports.setup_env = setup_env; module.exports.el_script = el_script; module.exports.e_call = e_call; module.exports.hide_cmd = hide_cmd; module.exports.cmd_count = cmd_count; ================================================ FILE: test/color/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/color/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "color" "0.0.1" "Test to print color on the terminal") (package-file "color.el") (source 'gnu) (depends-on "emacs" "26.1") ================================================ FILE: test/color/color.el ================================================ ;;; color.el --- Test to print color on the terminal -*- lexical-binding: t; -*- ;; Version: 0.0.1 ;; Package-Requires: ((emacs "26.1")) ;;; Commentary: ;;; Code: (let ((eask-verbosity 99) (eask-timestamps t) (eask-log-level t) (eask--ignore-error-p t)) (eask-debug "This is %s message" (ansi-magenta "DEBUG")) (eask-log "This is %s message" (ansi-magenta "LOG")) (eask-info "This is %s message" (ansi-magenta "INFO")) (eask-warn "This is %s message" (ansi-magenta "WARNING")) (eask-error "This is %s message" (ansi-magenta "ERROR"))) ;;; color.el ends here ================================================ FILE: test/color/run.sh ================================================ #!/usr/bin/env bash # Copyright (C) 2022-2026 the Eask authors. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Commentary: # # Test color # set -e echo "Test color" cd $(dirname "$0") eask load color.el eask load color.el --no-color ================================================ FILE: test/development/compat.el ================================================ ;;; compat.el --- Test Emacs compatibility -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Test for Emacs' functions and variables existense. ;; ;; See the test result in ;; (https://github.com/emacs-eask/cli/actions/workflows/test-outdated_upgrade.yml). ;; ;;; Code: (require 'finder) (require 'thingatpt) ;; ;;; Test functions (defconst compat-functions '( ansi-color-filter-apply thing-at-point--read-from-whole-string ls-lisp-format-file-size lsh package--alist package--activate-all package-activate-all package-generate-description-file locate-dominating-file url-file-exists-p prin1-to-string kill-current-buffer) "List of function to check Emacs compatibility.") (message "Starting compatibility test for functions...") (dolist (func compat-functions) (let ((exists-p)) (eask-with-progress (format " - Checked function %s... " (ansi-green (eask-2str func))) (setq exists-p (fboundp func)) (if exists-p "done ✓" "missing ✗")) (unless exists-p (eask-error "Missing function %s in Emacs version %s... " func emacs-version)))) ;; ;;; Test variables (defconst compat-variables '( finder-known-keywords package-quickstart-file print-level print-length) "List of variables to check Emacs compatibility.") (message "Starting compatibility test for variables...") (dolist (var compat-variables) (let ((exists-p)) (eask-with-progress (format " - Checked variable %s... " (ansi-green (eask-2str var))) (setq exists-p (boundp var)) (if exists-p "done ✓" "missing ✗")) (unless exists-p (eask-error "Missing variable %s in Emacs version %s... " var emacs-version)))) ;;; compat.el ends here ================================================ FILE: test/development/compile/test-redefine.el ================================================ ;;; test-redefine.el --- Test redefine -*- lexical-binding: t; -*- ;;; Commentary: ;; ;; Test the concatened file built from `eask concat`. ;; ;; We are only going to check for rule `redefine', since this isn't a regular ;; Emacs package. ;; ;;; Code: (setq byte-compile-error-on-warn t) (let* ((concated-file "./dist/eask.built.el") (concated-file (expand-file-name concated-file)) (conditions " ;; Local Variables: ;; coding: utf-8 ;; byte-compile-warnings: (redefine) ;; End: ")) (with-current-buffer (find-file concated-file) (unless (string-suffix-p conditions (buffer-string)) (goto-char (point-max)) (insert conditions)) (save-buffer) (byte-compile-file buffer-file-name))) ;; Local Variables: ;; coding: utf-8 ;; no-byte-compile: t ;; End: ;;; test-redefine.el ends here ================================================ FILE: test/error/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/error/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "error" "0.0.1" "Test (trigger) error on GitHub Actions") (source 'gnu) (source 'melpa) (depends-on "emacs" "999.99") ; This is the error we are going to see! ================================================ FILE: test/error/run.sh ================================================ #!/usr/bin/env bash # Copyright (C) 2022-2026 the Eask authors. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Commentary: # # Test (trigger) error on GitHub Actions # set -e echo "Test (trigger) error on GitHub Actions" cd $(dirname "$0") # This should trigger an error! eask info ================================================ FILE: test/fixtures/home/.emacs.d/.gitignore ================================================ # settings /site-lisp/custom.el README.html *.bak *.data *.db *.elc *.log *.sqlite *.tmp *bak/ .DS_Store .cache/ .cask/ .dap-* .extension/ .vscode/ /.agignore /.elfeed /.emacs*.desktop /.emacs*.desktop.lock /.emacs-buffers /.last-package-update-day /.lsp-session* /.markdown-preview.html /.mc-lists.el /.org-id-locations /.pdf-view-restore /.persistent-scratch /.rgignore /ac-comphist.dat /amx-items /bookmarks /company-statistics-cache.el /custom-post.el /history /nov-places /package-quickstart.el /places /projectile-bookmarks.eld /projectile.cache /projects /recentf /scratch /smex-items /tramp anaconda-mode/ auto-save-list/ cnfonts/ devdocs-browser/ devdocs/ eclipse.jdt.ls/ eln-cache/ elpa-devel/ elpa/ emojis/ eshell/ flycheck_*.el* games/ ido.* image-dired/ images/ irony/ librime/ magithub/ mspyls/ newsticker/ persistent-scratch/ persp-confs/ rcirc-log/ request/ rime/ rust-playground/ server/ session* snippets/ thumbs/ tldr/ tmp* transient/ tutorial/ url/ var/ workspace/ # tags /GPATH /GRTAGS /GTAGS /TAGS # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/fixtures/home/.emacs.d/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package ".emacs.d" "0.0.1" "Minimal test configuration") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/home/.emacs.d") (keywords "test") (depends-on "emacs" "26.1") (depends-on "csharp-mode") (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/fixtures/home/.emacs.d/RADME.md ================================================ Minimal Emacs configuration to run `eask`; only for testing purposes! The tests are mainly when the option `--config`, `-c` is on. ================================================ FILE: test/fixtures/home/.emacs.d/init.el ================================================ ;;; init.el --- Load the full configuration -*- lexical-binding: t -*- ;;; Commentary: ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/") ("nongnu" . "http://elpa.nongnu.org/nongnu/") ("melpa" . "http://melpa.org/packages/") ("jcs-elpa" . "https://jcs-emacs.github.io/jcs-elpa/packages/")) package-archive-priorities '(("gnu" . 0) ("nongnu" . 0) ("melpa" . 5) ("jcs-elpa" . 10))) (setq package-enable-at-startup nil ; To avoid initializing twice package-check-signature nil) (require 'package) ;; Local Variables: ;; coding: utf-8 ;; no-byte-compile: t ;; End: ;;; init.el ends here ================================================ FILE: test/fixtures/home/.gitignore ================================================ !.eask ================================================ FILE: test/fixtures/home/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "home" "0.0.1" "Minimal test for global config") (website-url "https://github.com/emacs-eask/mini.eask") (keywords "test") (source 'gnu) (source 'melpa) (depends-on "emacs" "26.1") (depends-on "yaml-mode") (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/fixtures/home/scripts/setup.ps1 ================================================ # Copyright (C) 2023 Jen-Chieh Shen # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Commentary: # # Copy configuration over to ~/.emacs.d/ # echo "Copy test configuration" mkdir "$env:USERPROFILE/AppData/Roaming/.eask" robocopy /e "./test/fixtures/home/_eask/" "$env:USERPROFILE/AppData/Roaming/.eask" robocopy /e "./test/fixtures/home/Eask" "$env:USERPROFILE/AppData/Roaming/Eask" echo "Copy test configuration" mkdir "$env:USERPROFILE/AppData/Roaming/.emacs.d" robocopy /e "./test/fixtures/home/.emacs.d/" "$env:USERPROFILE/AppData/Roaming/.emacs.d" ================================================ FILE: test/fixtures/home/scripts/setup.sh ================================================ #!/usr/bin/env bash # Copyright (C) 2023-2026 the Eask authors. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Commentary: # # Copy global .eask over to ~/.eask/ # echo "Copy test .eask" cp -R ./test/fixtures/home/_eask/ ~/.eask cp -R ./test/fixtures/home/Eask ~/Eask echo "Copy test configuration" cp -R ./test/fixtures/home/.emacs.d/ ~/.emacs.d ================================================ FILE: test/fixtures/home/scripts/testing.sh ================================================ #!/usr/bin/env bash # Copyright (C) 2025-2026 the Eask authors. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Commentary: # # Test reporting functions # This file will be sourced by other shell test scripts. # Expects to run with set -e # TODO: note that eask output is always to stderr # Run command and exit with the same status # All args interpreted as program to run # e.g. should_run cmd arg1 arg2 ... should_run() { echo "Run: $*" echo "---------------" "$@" 2>&1 | cat local TEST_STATUS=${PIPESTATUS[0]} echo "" if [ $TEST_STATUS -gt 0 ]; then printf "Fail: \'%s\' exited with status %s\n" "$*" "$TEST_STATUS" >&2 exit 1 fi } # Run command and exit with non-zero if command exits with zero. # All args interpreted as program to run # e.g. should_error cmd arg1 arg2 ... should_error() { echo "Run: $*" echo "---------------" # a shorter alternative is # ! "$@" || (echo "failed" && exit 1) # this version allow for more formatting "$@" 2>&1 | cat # Exit status of individual commands in a pipeline are ignored local TEST_STATUS=${PIPESTATUS[0]} # Get the true status, $? will always be 0 echo "" if [ $TEST_STATUS -eq 0 ]; then printf "Fail: \'%s\' should exit with non-zero status\n" "$*" >&2 exit 1 fi } # Check if (grep) pattern ARG1 is found in string ARG2 # E.g. should_match "foo*" "foobar" should_match() { echo "$2" | grep "$1" - | cat > /dev/null local SEARCH_RES=${PIPESTATUS[1]} if [ $SEARCH_RES -gt 0 ]; then printf "Fail: Output did not match \'%s\'\n" "$1" exit 1 fi } ================================================ FILE: test/fixtures/mini.pkg.1/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "mini.pkg.1" "0.0.1" "Minimal test package") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1") (keywords "test" "local") (package-file "mini.pkg.1.el") (files "files/*.el") (script "test" "echo \"Have a nice day!~ ;)\"") (script "extra" "echo :") (script "info" "eask info") (eask-defcommand mini-test-1 "Test command 1." (message "Test 1")) (eask-defcommand mini-test-2 "Test command 2." (message "Test 2")) (eask-defcommand mini-test-3 "Test command 3." (message "Test 3: %s" eask-rest)) (source 'gnu) (source 'melpa) (source 'jcs-elpa) (depends-on "emacs" "26.1") (depends-on "f") (depends-on "s") (depends-on "fringe-helper") (depends-on "watch-cursor" :repo "jcs-elpa/watch-cursor" :fetcher 'github) (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/fixtures/mini.pkg.1/RADME.md ================================================ Minimal Emacs package to simulate development environment; only for testing purposes! ================================================ FILE: test/fixtures/mini.pkg.1/files/mini.pkg.1-1.el ================================================ ;;; mini.pkg.1-1.el --- Extern file 1 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-1.el ;; ;;; Code: (defun mini.pkg.1-1 () "Test function 1." (interactive) ) (provide 'mini.pkg.1-1) ;;; mini.pkg.1-1.el ends here ================================================ FILE: test/fixtures/mini.pkg.1/files/mini.pkg.1-2.el ================================================ ;;; mini.pkg.1-2.el --- Extern file 2 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-2.el ;; ;;; Code: (defun mini.pkg.1-2 () "Test function 2." (interactive) ) (provide 'mini.pkg.1-2) ;;; mini.pkg.1-2.el ends here ================================================ FILE: test/fixtures/mini.pkg.1/mini.pkg.1.el ================================================ ;;; mini.pkg.1.el --- Minimal test package -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1 ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test local ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.1) ;;; mini.pkg.1.el ends here ================================================ FILE: test/fixtures/mini.pkg.2/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "mini.pkg.2" "0.0.1" "Minimal test package") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.2") (keywords "test") (package-file "mini.pkg.2.el") (files "files/*.el") (script "test" "echo \"Have a nice day!~ ;)\"") (script "info" "eask info") (source 'gnu) (source 'melpa) (source 'jcs-elpa) (depends-on "emacs" "26.1") (depends-on "f") (depends-on "s") (depends-on "fringe-helper") (depends-on "watch-cursor" :repo "jcs-elpa/watch-cursor" :fetcher 'github) (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/fixtures/mini.pkg.2/RADME.md ================================================ Minimal Emacs package to simulate development environment; only for testing purposes! ================================================ FILE: test/fixtures/mini.pkg.2/files/mini.pkg.2-1.el ================================================ ;;; mini.pkg.2-1.el --- Extern file 1 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.2-1.el ;; ;;; Code: (defun mini.pkg.2-1 () "Test function 1." (interactive) ) (provide 'mini.pkg.2-1) ;;; mini.pkg.2-1.el ends here ================================================ FILE: test/fixtures/mini.pkg.2/files/mini.pkg.2-2.el ================================================ ;;; mini.pkg.2-2.el --- Extern file 2 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.2-2.el ;; ;;; Code: (defun mini.pkg.2-2 () "Test function 2." (interactive) ) (provide 'mini.pkg.2-2) ;;; mini.pkg.2-2.el ends here ================================================ FILE: test/fixtures/mini.pkg.2/mini.pkg.2.el ================================================ ;;; mini.pkg.2.el --- Minimal test package -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.2 ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.2) ;;; mini.pkg.2.el ends here ================================================ FILE: test/jest/README.md ================================================ # Jest Testing See the full documentation in https://emacs-eask.github.io/Contributing/Developing-Eask/#-testing. ================================================ FILE: test/jest/__snapshots__/analyze.test.js.snap ================================================ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`analyze in ./analyze/dsl handles json option 1`] = ` { "stderr": " { "warnings": [ { "range": { "start": { "line": 25, "col": 0, "pos": 853 }, "end": { "line": 25, "col": 35, "pos": 888 } }, "filename": "~/Eask", "message": "💡 Pkg-file seems to be missing \`check-pkg.el'" }, { "range": { "start": { "line": 20, "col": 0, "pos": 709 }, "end": { "line": 20, "col": 17, "pos": 726 } }, "filename": "~/Eask", "message": "💡 Warning regarding duplicate license name, GPLv3" }, { "range": { "start": { "line": 17, "col": 0, "pos": 613 }, "end": { "line": 17, "col": 15, "pos": 628 } }, "filename": "~/Eask", "message": "💡 Warning regarding duplicate author name, name" } ], "errors": [ { "range": { "start": { "line": 46, "col": 0, "pos": 1696 }, "end": { "line": 50, "col": 2, "pos": 1908 } }, "filename": "~/Eask", "message": "✗ Define dependencies with the same name \`f' with different version" }, { "range": { "start": { "line": 46, "col": 0, "pos": 1696 }, "end": { "line": 50, "col": 2, "pos": 1908 } }, "filename": "~/Eask", "message": "✗ Define dependencies with the same name \`f'" }, { "range": { "start": { "line": 44, "col": 0, "pos": 1596 }, "end": { "line": 44, "col": 27, "pos": 1623 } }, "filename": "~/Eask", "message": "✗ Define dependencies with the same name \`dash' with different version" }, { "range": { "start": { "line": 43, "col": 0, "pos": 1576 }, "end": { "line": 43, "col": 19, "pos": 1595 } }, "filename": "~/Eask", "message": "✗ Define dependencies with the same name \`dash'" }, { "range": { "start": { "line": 40, "col": 0, "pos": 1474 }, "end": { "line": 40, "col": 20, "pos": 1494 } }, "filename": "~/Eask", "message": "✗ Define dependencies with the same name \`emacs'" }, { "range": { "start": { "line": 37, "col": 0, "pos": 1304 }, "end": { "line": 37, "col": 15, "pos": 1319 } }, "filename": "~/Eask", "message": "✗ Unknown package archive \`local'" }, { "range": { "start": { "line": 37, "col": 0, "pos": 1304 }, "end": { "line": 37, "col": 15, "pos": 1319 } }, "filename": "~/Eask", "message": "✗ Invalid archive name \`local'" }, { "range": { "start": { "line": 35, "col": 0, "pos": 1229 }, "end": { "line": 35, "col": 24, "pos": 1253 } }, "filename": "~/Eask", "message": "✗ Unknown package archive \`magic-archive'" }, { "range": { "start": { "line": 33, "col": 0, "pos": 1214 }, "end": { "line": 33, "col": 13, "pos": 1227 } }, "filename": "~/Eask", "message": "✗ Multiple definition of source \`gnu'" }, { "range": { "start": { "line": 30, "col": 0, "pos": 1073 }, "end": { "line": 30, "col": 61, "pos": 1134 } }, "filename": "~/Eask", "message": "✗ Run-script with the same key name is not allowed: \`test\`" }, { "range": { "start": { "line": 26, "col": 0, "pos": 953 }, "end": { "line": 26, "col": 35, "pos": 988 } }, "filename": "~/Eask", "message": "✗ Multiple definition of \`package-descriptor'" }, { "range": { "start": { "line": 23, "col": 0, "pos": 822 }, "end": { "line": 23, "col": 29, "pos": 851 } }, "filename": "~/Eask", "message": "✗ Multiple definition of \`package-file'" }, { "range": { "start": { "line": 14, "col": 0, "pos": 517 }, "end": { "line": 14, "col": 16, "pos": 533 } }, "filename": "~/Eask", "message": "✗ Multiple definition of \`keywords'" }, { "range": { "start": { "line": 12, "col": 0, "pos": 383 }, "end": { "line": 12, "col": 55, "pos": 438 } }, "filename": "~/Eask", "message": "✗ Multiple definition of \`website-url'" }, { "range": { "start": { "line": 9, "col": 0, "pos": 290 }, "end": { "line": 9, "col": 18, "pos": 308 } }, "filename": "~/Eask", "message": "✗ Multiple definition of \`package'" } ] } (Checked 1 file) ", "stdout": "", } `; exports[`analyze in ./analyze/dsl matches snapshot 1`] = ` { "stderr": " ~/Eask:9:18 Error: ✗ Multiple definition of \`package' ~/Eask:12:55 Error: ✗ Multiple definition of \`website-url' ~/Eask:14:16 Error: ✗ Multiple definition of \`keywords' ~/Eask:17:15 Warning: 💡 Warning regarding duplicate author name, name ~/Eask:20:17 Warning: 💡 Warning regarding duplicate license name, GPLv3 ~/Eask:23:29 Error: ✗ Multiple definition of \`package-file' ~/Eask:25:35 Warning: 💡 Pkg-file seems to be missing \`check-pkg.el' ~/Eask:26:35 Error: ✗ Multiple definition of \`package-descriptor' ~/Eask:30:61 Error: ✗ Run-script with the same key name is not allowed: \`test\` ~/Eask:33:13 Error: ✗ Multiple definition of source \`gnu' ~/Eask:35:24 Error: ✗ Unknown package archive \`magic-archive' ~/Eask:37:15 Error: ✗ Invalid archive name \`local' ~/Eask:37:15 Error: ✗ Unknown package archive \`local' ~/Eask:40:20 Error: ✗ Define dependencies with the same name \`emacs' ~/Eask:43:19 Error: ✗ Define dependencies with the same name \`dash' ~/Eask:44:27 Error: ✗ Define dependencies with the same name \`dash' with different version ~/Eask:50:2 Error: ✗ Define dependencies with the same name \`f' ~/Eask:50:2 Error: ✗ Define dependencies with the same name \`f' with different version (Checked 1 file) ", "stdout": "", } `; ================================================ FILE: test/jest/analyze/dsl/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/analyze/dsl/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ;; ;;; Below are errors/warnings to lint (package "check-dsl" "0.0.1" "Test for DSL") ; duplicate `package` directive (package "" "" "") (website-url "https://github.com/emacs-eask/check-dsl") ; duplicate url (website-url "https://github.com/emacs-eask/check-dsl") (keywords "dsl") ; duplicate keywords (keywords "dsl") (author "name") ; duplicate `author` (author "name") (license "GPLv3") ; duplicate `license` (license "GPLv3") (package-file "check-dsl.el") ; duplicate `package-file` directive (package-file "check-dsl.el") (package-descriptor "check-pkg.el") ; duplicate `package-descriptor` directive (package-descriptor "check-pkg.el") ;; duplicate scripts (script "test" "echo \"Error: no test specified\" && exit 1") (script "test" "echo \"Error: no test specified\" && exit 1") (source 'gnu) ; duplicate archives (source 'gnu) (source "magic-archive") ; unkown archive (source 'local) ; inavlid local archive (depends-on "emacs" "26.1") ; duplicate `emacs` dependency (depends-on "emacs") (depends-on "dash") ; duplicate dependency (depends-on "dash") (depends-on "dash" "9.9.9") ; duplicate dependency; different version (development (depends-on "f") ; duplicate dependency (depends-on "f") (depends-on "f" "9.9.9") ; duplicate dependency; different version ) ================================================ FILE: test/jest/analyze/dsl/check-dsl.el ================================================ ;;; check-dsl.el --- Test for DSL -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-04-19 15:16:50 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/check-dsl ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (dash "0.1")) ;; Keywords: dsl ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Test for DSL. ;; ;;; Code: (provide 'check-dsl) ;;; check-dsl.el ends here ================================================ FILE: test/jest/analyze/errors/Eask-error ================================================ (package "" "" "") (scrog "unrecognized") ================================================ FILE: test/jest/analyze/errors/Eask-lexical ================================================ (package "check-dsl" "0.0.1" "Test for DSL") (keywords "dsl") ================================================ FILE: test/jest/analyze/errors/Eask-normal ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "check-dsl" "0.0.1" "Test for DSL") (keywords "dsl") ================================================ FILE: test/jest/analyze/errors/Eask-warn ================================================ (package "check-dsl" "0.0.1" "Test for DSL") (keywords "dsl") (package-file "none.el") ================================================ FILE: test/jest/analyze/metadata/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/analyze/metadata/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ;; ;;; Below are errors/warnings to lint (package "check-metadata?" ; unmatched "0.0.1?" ; unmatched and invalid version syntax "Test for unmatch metadata?" ; unmatched ) (website-url "https://github.com/emacs-eask/check-metadata?") ; unmatched (keywords "metadata?") ; missing (package-file "check-metadata.el") (depends-on "s") ; unmatch dependency ================================================ FILE: test/jest/analyze/metadata/check-metadata.el ================================================ ;;; check-metadata.el --- Test for unmatch metadata -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-04-19 15:16:50 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/check-metadata ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (dash "0.1")) ;; Keywords: metadata ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Test for unmatch metadata. ;; ;;; Code: (provide 'check-metadata) ;;; check-metadata.el ends here ================================================ FILE: test/jest/analyze.test.js ================================================ const { TestContext } = require("./helpers"); /** * Clean output and attempt to parse as JSON. * Throws if failing. * @param {string} s * @returns {object} the parsed JSON. */ function tryJSON(s) { // The main use case of analyze --json is flycheck/flymake // both of which accept mixed JSON/text output. // So the test should filter non-JSON output in a similar way // See flycheck--json-parser for reference // This is a rough approximation of the greedy JSON parsing // that flycheck uses // It will yield a false negative if: // - a non-JSON output line begins with a space // - there is more that one JSON object in the output const lines = s.split("\n"); const json = lines.filter((x) => x.match("^[{}\\[\\] ]")); if (json.length == 0) { return {}; } else { const result = JSON.parse(json.join("\n")); if (typeof result == "string") { // this case is a single string prefixed with whitespace return {}; } return result; } } describe("analyze", () => { describe("in ./analyze/dsl", () => { const ctx = new TestContext("./test/jest/analyze/dsl"); it("handles plain text", async () => { await expect(ctx.runEask("analyze")).rejects.toThrow(); await expect(ctx.runEask("analyze Eask")).rejects.toThrow(); }); it("handles json option", async () => { // alternate command order await expect(ctx.runEask("analyze Eask --json")).rejects.toThrow(); try { await ctx.runEask("analyze --json"); } catch (e) { tryJSON(e.stderr); const res = ctx.errorToCommandOutput(e); const resClean = res.sanitized().raw(); expect(resClean).toMatchSnapshot(); } }); it("matches snapshot", async () => { try { await ctx.runEask("analyze"); expect.failing(); } catch (e) { expect(e.code).toBe(1); const res = ctx.errorToCommandOutput(e); const resClean = res.sanitized().raw(); expect(resClean).toMatchSnapshot(); } }); it("should report multiple definitions", async () => { try { await ctx.runEask("analyze"); expect.failing(); } catch ({ stderr }) { // expect this substring expect(stderr).toMatch("Multiple definition of `package'"); } }); }); describe("in ./empty", () => { const ctx = new TestContext("./test/jest/empty"); it("should error", async () => { await expect(ctx.runEask("analyze")).rejects.toThrow(); }); }); describe("in ./analyze/metadata", () => { const ctx = new TestContext("./test/jest/analyze/metadata"); it("handles plain text", async () => { await expect(ctx.runEask("analyze")).rejects.toThrow(); await expect(ctx.runEask("analyze Eask")).rejects.toThrow( expect.objectContaining({ code: 1, stderr: expect.stringContaining("(Checked 1 file)"), }), ); }); it("handles json", async () => { await expect(ctx.runEask("analyze --json")).rejects.toThrow(); await expect(ctx.runEask("analyze Eask --json")).rejects.toMatchObject({ code: 1, stderr: expect.stringContaining("(Checked 1 file)"), }); }); }); describe("in ./analyze/errors", () => { const ctx = new TestContext("./test/jest/analyze/errors"); // Eask-normal - no errors or warnings // Eask-warn - only warnings // Eask-error - errors and warnings it("should check Eask-normal", async () => { await ctx.runEask("analyze Eask-normal"); }); it("should check Eask-warn", async () => { await ctx.runEask("analyze Eask-warn"); }); it("should error on Eask-errors", async () => { await expect(ctx.runEask("analyze Eask-error")).rejects.toThrow(); }); // JSON it("handles json option when no errors", async () => { const { stderr } = await ctx.runEask("analyze --json Eask-normal"); tryJSON(stderr); }); it("handles json option when lexical binding warnings are present", async () => { const { stderr } = await ctx.runEask("analyze --json Eask-lexical"); tryJSON(stderr); }); // --strict and --allow-error it("should error when using --strict on Eask-warn", async () => { await expect(ctx.runEask("analyze --strict Eask-warn")).rejects.toThrow(); }); // sanity check: flag should not change behavior in this case // this is because the menaing of --allow-error is "continue to the end" it("should error when --allow-error is set", async () => { await expect( ctx.runEask("analyze Eask-error --allow-error"), ).rejects.toThrow(); }); it("should print Checked when there are errors", async () => { await expect(ctx.runEask("analyze Eask-error")).rejects.toMatchObject({ stderr: expect.stringContaining("(Checked 1 file)"), }); }); it("should check all files when --allow-error is set", async () => { // this is not a great test because when there is any error it doesn't print this note await expect( ctx.runEask("analyze --allow-error Eask-normal Eask-error"), ).rejects.toMatchObject({ stderr: expect.stringContaining("(Checked 2 files)"), }); }); // although the output does match, it still doesn't exit with an error it("should have later warnings when --allow-error is set", async () => { await expect( ctx.runEask("analyze --allow-error Eask-error Eask-warn"), // this warning is specific to Eask-warn ).rejects.toMatchObject({ stderr: expect.stringContaining("missing `none.el'"), }); }); }); }); ================================================ FILE: test/jest/buttercup/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/buttercup/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "buttercup" "0.0.1" "Test project for command `buttercup'") (package-file "buttercup.el") (source 'gnu) (source 'melpa) (development (depends-on "buttercup")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/buttercup/test-fail/buttercup-test.el ================================================ ;;; buttercup-test.el --- Test the command buttercup -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command buttercup ;;; Code: (require 'buttercup) (require 'debug) (describe "A failing suite" (it "contains a spec with a false expectation" (expect t :to-be nil))) ;;; buttercup-test.el ends here ================================================ FILE: test/jest/buttercup/test-ok/buttercup-test.el ================================================ ;;; buttercup-test.el --- Test the command buttercup -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command buttercup ;;; Code: (require 'buttercup) (require 'debug) (describe "A suite" (it "contains a spec with an expectation" (expect t :to-be t))) ;;; buttercup-test.el ends here ================================================ FILE: test/jest/compile/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "check" "0.0.1" "mock package") ================================================ FILE: test/jest/compile/fail.el ================================================ ;; -*- lexical-binding: t -*- ;; this must fail to compile (fail "" ( ================================================ FILE: test/jest/compile/mock.el ================================================ ;;;###autoload (defun my-ignore-fn () "Mock for testing" (interactive) (message "foo")) ================================================ FILE: test/jest/compile.test.js ================================================ const { TestContext } = require("./helpers"); describe("compile", () => { const ctx = new TestContext("./test/jest/compile"); afterAll(() => { ctx.cleanUp(); }); describe("in an empty project", () => { const ctxEmpty = new TestContext("./test/jest/empty"); it("should error on partial input", async () => { await expect(ctxEmpty.runEask("compile")).rejects.toThrow(); }); }); describe("for compile/mock.el", () => { it("should compile with a warning", async () => { await ctx.runEask("compile mock.el"); }); it("should escalate warnings given --strict", async () => { await expect( ctx.runEask("compile --strict mock.el"), ).rejects.toMatchObject({ code: 1 }); }); }); describe("for compile/fail.el", () => { beforeEach(async () => { await ctx.runEask("clean elc"); }); it("should error", async () => { await expect(ctx.runEask("compile fail.el")).rejects.toMatchObject({ code: 1, }); }); it("should compile mock.el given --allow-error", async () => { await expect( ctx.runEask("compile --allow-error fail.el mock.el"), ).rejects.toMatchObject({ code: 1, stderr: expect.stringContaining("1 file compiled, 1 skipped"), }); expect(ctx.fileExists("./mock.elc")); }); }); }); ================================================ FILE: test/jest/config/init.el ================================================ ;;; init.el --- Load the full configuration -*- lexical-binding: t -*- ;;; Commentary: ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/") ("nongnu" . "http://elpa.nongnu.org/nongnu/") ("melpa" . "http://melpa.org/packages/") ("jcs-elpa" . "https://jcs-emacs.github.io/jcs-elpa/packages/")) package-archive-priorities '(("gnu" . 0) ("nongnu" . 0) ("melpa" . 5) ("jcs-elpa" . 10))) (setq package-enable-at-startup nil ; To avoid initializing twice package-check-signature nil) (require 'package) ;; Local Variables: ;; coding: utf-8 ;; no-byte-compile: t ;; End: ;;; init.el ends here ================================================ FILE: test/jest/config.test.js ================================================ const { testUnsafe, TestContext } = require("./helpers"); const fs = require("node:fs/promises"); const path = require("node:path"); const process = require("node:process"); describe("config param", () => { const ctx = new TestContext(); afterAll(() => ctx.cleanUp()); describe("informational", () => { /* * Note that these tests will run against your ~/.emacs.d if run locally. * If you have a complicated set up, they may timeout. */ test("eask archives -c", async () => { await ctx.runEask("archives -c"); }); test("eask list -c", async () => { await ctx.runEask("list -c --depth=0"); }); test("eask outdated -c", async () => { await ctx.runEask("outdated -c"); }); }); describe("install", () => { /* * This test modifies ~/.emacs.d/init.el and installs packages there when run with ALLOW_UNSAFE */ testUnsafe("eask install -c", async () => { // create ~/.emacs.d await fs.mkdir(path.join(process.env.HOME, "/.emacs.d/"), { recursive: true, }); // copy init.el from fixtures // it must enable sources for the installed packages await fs.copyFile( "./test/jest/config/init.el", path.join(process.env.HOME, "/.emacs.d/init.el"), fs.constants.COPYFILE_EXCL, // throw if init.el already exists ); await ctx.runEask("install -c spinner ivy beacon company-fuzzy", { timeout: 120000, }); }); testUnsafe("eask uninstall -c", async () => { await ctx.runEask("uninstall -c ivy company-fuzzy"); }); }); }); ================================================ FILE: test/jest/docker/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "mini.pkg.1" "0.0.1" "Minimal test package") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1") (keywords "test" "local") (package-file "mini.pkg.1.el") (files "files/*.el") (script "test" "echo \"Have a nice day!~ ;)\"") (script "extra" "echo :") (script "info" "eask info") (eask-defcommand mini-test-1 "Test command 1." (message "Test 1")) (eask-defcommand mini-test-2 "Test command 2." (message "Test 2")) (eask-defcommand mini-test-3 "Test command 3." (message "Test 3: %s" eask-rest)) (source 'gnu) (source 'melpa) (source 'jcs-elpa) (depends-on "emacs" "26.1") (depends-on "f") (depends-on "s") (depends-on "fringe-helper") (depends-on "watch-cursor" :repo "jcs-elpa/watch-cursor" :fetcher 'github) (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/docker/RADME.md ================================================ Minimal Emacs package to simulate development environment; only for testing purposes! ================================================ FILE: test/jest/docker/files/mini.pkg.1-1.el ================================================ ;;; mini.pkg.1-1.el --- Extern file 1 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-1.el ;; ;;; Code: (defun mini.pkg.1-1 () "Test function 1." (interactive) ) (provide 'mini.pkg.1-1) ;;; mini.pkg.1-1.el ends here ================================================ FILE: test/jest/docker/files/mini.pkg.1-2.el ================================================ ;;; mini.pkg.1-2.el --- Extern file 2 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-2.el ;; ;;; Code: (defun mini.pkg.1-2 () "Test function 2." (interactive) ) (provide 'mini.pkg.1-2) ;;; mini.pkg.1-2.el ends here ================================================ FILE: test/jest/docker/mini.pkg.1.el ================================================ ;;; mini.pkg.1.el --- Minimal test package -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1 ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test local ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.1) ;;; mini.pkg.1.el ends here ================================================ FILE: test/jest/docker.test.js ================================================ const { TestContext } = require("./helpers"); describe("docker", () => { const ctx = new TestContext("./test/jest/docker"); afterAll(() => ctx.cleanUp()); test("eask docker 27.1 info", async () => { // this can take a long time to pull and build the image await ctx.runEask("docker 27.1 info", { timeout: 40000 }); }); test("eask docker silex/emacs:27.1-eask info", async () => { // this can take a long time to pull and build the image await ctx.runEask("docker silex/emacs:27.1-eask info", { timeout: 40000 }); }); }); ================================================ FILE: test/jest/ecukes/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "ecukes" "0.0.1" "Test project for command `ecukes'") (package-file "ecukes.el") (source 'gnu) (source 'melpa) (development (depends-on "ecukes")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/ecukes/features/foo.feature ================================================ ================================================ FILE: test/jest/ecukes/features/step-definitions/ecukes-steps.el ================================================ ================================================ FILE: test/jest/ecukes/features/support/env.el ================================================ ================================================ FILE: test/jest/emacs.test.js ================================================ const { TestContext } = require("./helpers"); describe("emacs", () => { const ctx = new TestContext("./test/jest/empty"); beforeAll(async () => await ctx.runEask("clean all")); test("eask emacs --version", async () => { await ctx.runEask("emacs --version"); }); test("eask emacs --batch --eval", async () => { await ctx.runEask( 'emacs --batch --eval "(require (quote ert))" --eval "(ert-deftest mytest () (should-not (display-graphic-p)))" -f ert-run-tests-batch', ); }); }); ================================================ FILE: test/jest/empty/.gitkeep ================================================ ================================================ FILE: test/jest/ert/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/ert/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "ert" "0.0.1" "Test project for command `ert'") (package-file "ert.el") (source 'gnu) (source 'melpa) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/ert/test/ert-test.el ================================================ ;;; ert-test.el --- Test the command ert -*- lexical-binding: t; -*- ;; Copyright (C) 2024-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command ert ;;; Code: (require 'ert) (require 'debug) (ert-deftest ert-test-1 () (should (= 1 1))) ;;; ert-test.el ends here ================================================ FILE: test/jest/ert/test-nil-message/ert-test.el ================================================ ;;; ert-test.el --- Test the command ert -*- lexical-binding: t; -*- ;; Copyright (C) 2024-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command ert ;;; Code: (require 'ert) (require 'debug) (ert-deftest ert-test-1 () (should-not (message nil))) ;;; ert-test.el ends here ================================================ FILE: test/jest/ert-runner/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/ert-runner/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "ert-runner" "0.0.1" "Test project for command `ert-runner'") (package-file "ert-runner.el") (source 'gnu) (source 'melpa) (development (depends-on "ert-runner")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/ert-runner/test/ert-runner-test.el ================================================ ;;; ert-runner-test.el --- Test the command ert-runner -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command ert-runner ;;; Code: (require 'ert) (require 'debug) (ert-deftest ert-runner-test-1 () (should (= 1 1))) (provide 'ert-runner-test) ;;; ert-runner-test.el ends here ================================================ FILE: test/jest/exec/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/exec/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "exec" "0.0.1" "Test project for command exec") (website-url "https://github.com/emacs-eask/exec") (keywords "exec") (package-file "exec.el") (exec-paths "./bin/") (source 'gnu) (source 'melpa) (depends-on "emacs" "26.1") (depends-on "ert-runner") (depends-on "github-elpa") (depends-on "buttercup") (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/exec/bin/github-elpa.bat ================================================ @echo off echo Test from github-elpa.bat ================================================ FILE: test/jest/exec/exec.el ================================================ ;;; exec.el --- Test project for command exec -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 14:10:40 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/exec ;; Version: 0.0.1 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: exec ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Test project for command exec. ;; ;;; Code: (require 'cl-lib) (provide 'exec) ;;; exec.el ends here ================================================ FILE: test/jest/exec/test/test-dummy.el ================================================ ;;; test-dummy.el --- Dummy buttercup test -*- lexical-binding: t; -*- ;; Copyright (C) 2023 the Eask authors. ;; This file is not part of GNU Emacs. ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Dummy buttercup test. ;; ;;; Code: (describe "Dumm Test" (it "pattern 1" (let ((dummy-var-1 t)) (expect dummy-var-1 :to-be t))) (it "pattern 2" (let ((dummy-var-2 nil)) (expect dummy-var-2 :to-be nil)))) (provide 'test-dummy) ;;; test-dummy.el ends here ================================================ FILE: test/jest/exec.test.js ================================================ const { emacsVersion, TestContext } = require("./helpers"); describe("exec", () => { const ctx = new TestContext("./test/jest/exec"); beforeAll(async () => { await ctx.runEask( "install-deps", { timeout: 40000 }); }); afterAll(() => ctx.cleanUp()); test("eask exec ert-runner", async () => { await ctx.runEask("exec ert-runner -h"); }); test("eask exec github-elpa", async () => { await ctx.runEask("exec github-elpa -h"); }); test("eask exec echo", async () => { await ctx.runEask("exec echo hello world"); }); test("eask exec buttercup -L .", async () => { await ctx.runEask("exec buttercup -L ."); }); test("eask exec buttercup -L . --pattern 'pattern 1'", async () => { await ctx.runEask('exec buttercup -L . --pattern "pattern 1"'); }); test("should error with no args", async () => { await expect(ctx.runEask("exec")).rejects.toThrow(); }); }); ================================================ FILE: test/jest/exit-status.test.js ================================================ const { TestContext } = require("./helpers"); describe("exit-status", () => { const ctx = new TestContext("./test/jest/empty"); afterAll(() => ctx.cleanUp()); describe("bump", () => { it("should error with no files", async () => { await expect(ctx.runEask("bump")).rejects.toThrow(); }); }); describe("concat", () => { it("should error with no files", async () => { await expect(ctx.runEask("concat")).rejects.toThrow(); }); }); describe("docs", () => { it("should error with no files", async () => { await expect(ctx.runEask("docs")).rejects.toThrow(); }); }); describe("eval", () => { it("should error with no files", async () => { await expect(ctx.runEask("eval")).rejects.toThrow(); }); }); describe("format", () => { it("elfmt should error with no files", async () => { await expect(ctx.runEask("format elfmt")).rejects.toThrow(); }); it("elisp-autofmt should error with no files", async () => { await expect(ctx.runEask("format elisp-autofmt")).rejects.toThrow(); }); }); describe("info", () => { it("should error with no files", async () => { // When run in the current directory this sees the eask file from the main project await expect(ctx.runEask("info -g")).rejects.toThrow(); }); }); describe("init", () => { it("should error with no files", async () => { await expect(ctx.runEask("concat")).rejects.toThrow(); }); }); describe("concat", () => { it("--from cask should error with no files", async () => { await expect(ctx.runEask("init --from cask")).rejects.toThrow(); }); it("--from keg should error with no files", async () => { await expect(ctx.runEask("init --from keg")).rejects.toThrow(); }); it("--from eldev should error with no files", async () => { await expect(ctx.runEask("init --from eldev")).rejects.toThrow(); }); it("--from source should error with no files", async () => { await expect(ctx.runEask("init --from source")).rejects.toThrow(); }); }); describe("lint", () => { it("checkdoc should error with no files", async () => { await expect(ctx.runEask("lint checkdoc")).rejects.toThrow(); }); it("declare should error with no files", async () => { await expect(ctx.runEask("lint declare")).rejects.toThrow(); }); it("elint should error with no files", async () => { await expect(ctx.runEask("lint elint")).rejects.toThrow(); }); it("elisp-lint should error with no files", async () => { await expect(ctx.runEask("lint elisp-lint")).rejects.toThrow(); }); // takes a while to install elsa it("elsa should error with no files", async () => { await expect(ctx.runEask("lint elsa")).rejects.toThrow(); }); it("indent should error with no files", async () => { await expect(ctx.runEask("lint indent")).rejects.toThrow(); }); it("keywords should error with no files", async () => { await expect(ctx.runEask("lint keywords")).rejects.toThrow(); }); it("regexps should error with no files", async () => { await expect(ctx.runEask("lint regexps")).rejects.toThrow(); }); }); describe("recipe", () => { it("should error with no files", async () => { await expect(ctx.runEask("recipe")).rejects.toThrow(); }); }); describe("run", () => { it("run command should error with no files", async () => { await expect(ctx.runEask("run command")).rejects.toThrow(); }); it("run script should error with no files", async () => { await expect(ctx.runEask("run script -g")).rejects.toThrow(); }); }); describe("source", () => { it("should error with no files", async () => { await expect(ctx.runEask("source")).rejects.toThrow(); }); it("add should error with no files", async () => { await expect(ctx.runEask("source add")).rejects.toThrow(); }); it("add should error with unknown package", async () => { await expect(ctx.runEask("source add foo")).rejects.toThrow(); }); it("delete should error with no files", async () => { await expect(ctx.runEask("source delete")).rejects.toThrow(); }); it("delete should error with unknown package", async () => { await expect(ctx.runEask("source delete foo")).rejects.toThrow(); }); }); }); ================================================ FILE: test/jest/global/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "home" "0.0.1" "Minimal test for global config") (website-url "https://github.com/emacs-eask/mini.eask") (keywords "test") (source 'gnu) (source 'melpa) (depends-on "emacs" "26.1") (depends-on "yaml-mode") (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/global.test.js ================================================ const fs = require("node:fs/promises"); const path = require("node:path"); const process = require("node:process"); const { testUnsafe, TestContext } = require("./helpers"); describe("global", () => { // global => install to ~/.eask const ctx = new TestContext(); describe("informational", () => { test("eask archives -g", async () => { await ctx.runEask("archives -g"); }); test("eask list -g --depth=0", async () => { await ctx.runEask("list -g --depth=0"); }); test("eask outdated -g", async () => { await ctx.runEask("outdated -g"); }); }); describe("install", () => { /* * This test modifies ~/.eask and ~/Eask */ beforeAll(async () => { // add a global Easkfile if (process.env.ALLOW_UNSAFE) { await fs.copyFile( "./test/jest/global/Eask", path.join(process.env.HOME, "Eask"), ); } }); afterAll(async () => { if (process.env.ALLOW_UNSAFE) { // remove test changes await ctx.runEask("uninstall -g spinner ivy beacon company-fuzzy", { timeout: 120000 }); await fs.rm(path.join(process.env.HOME, "Eask")); } }); testUnsafe("eask install -g", async () => { await ctx.runEask("install -g spinner ivy beacon company-fuzzy", { timeout: 120000 }); }); testUnsafe("eask uninstall -g", async () => { await ctx.runEask("uninstall -g ivy company-fuzzy"); }); }); }); ================================================ FILE: test/jest/helpers.js ================================================ const util = require("node:util"); const exec = util.promisify(require("node:child_process").exec); const fs = require("node:fs/promises"); const path = require("node:path"); // Load the environment. const env = require('../../src/env'); /* * This file uses JsDoc syntax. * Documentation for type helpers like @param: https://jsdoc.app/tags-type * Documentation for types in braces: * https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System */ /** * As for jest.test but skips running if the ALLOW_UNSAFE env var is set. * @param {string} name * @param {function({ fail: function }): void | function(): PromiseLike.} fn * @param {number} [timeout] */ function testUnsafe(name, fn, timeout) { if (process.env.ALLOW_UNSAFE) { return test(name, fn, timeout); } else { return test.skip(name, fn, timeout); } } /** * Global timeout in ms, set by TIMEOUT env var. * This is used in TestContext to limit `exec`. * @returns {number} */ function getTimeout() { // parseInt returns NaN if it fails // TIMEOUT <= 0 will be ignored too return Number.parseInt(process.env.TIMEOUT) || 25000; } /** * The version string for system emacs, e.g. "30.0.50". * You can compare lexicographically, e.g. if ((await emacsVersion()) > "27") { ... } * @returns {Promise.} emacs version string. */ async function emacsVersion() { const text = await exec("emacs --version"); const version = text.stdout.match("Emacs ([^ ]+)")?.[1]; if (!version) { throw Error("Couldn't extract Emacs version. Text was:\n" + text); } return version; } /** * Remove all ansi codes from string. * @returns {string} */ async function stripAnsi(s) { return s.replace(/\u001b[^m]*?m/g, ""); } /** Provides transformations on output of node.exec(). */ class CommandOutput { constructor(output, cwd) { this.stderr = output.stderr; this.stdout = output.stdout; this.cwd = cwd; this.cwdAbsolute = path.resolve(cwd || "."); } /** * Both stdout and stderr concatenated as a string. * @returns {string} */ combined() { return this.stdout + "\n" + this.stderr; } /** * Output as a plain object. * @returns {{ sdout: string, stderr: string }} */ raw() { return { stderr: this.stderr, stdout: this.stdout, }; } /** * Output with no color. * @returns {{ sdout: string, stderr: string }} */ rawNoColor() { return { stderr: stripAnsi(this.stderr), stdout: stripAnsi(this.stdout), }; } /** * Attempt to make `s` safe for snapshotting by replacing local data. * @param {string} s * @returns {string} */ sanitizeString(s) { let working = s; // windows paths if (this.cwdAbsolute.startsWith(":", 1)) { // cwdAbsolute is a windows path e.g. C:\foo\bar // Eask outputs windows paths like d:/a/cli/Eask let cwdTranslated = this.cwdAbsolute.replaceAll("\\", "/"); // lowercase the drive letter cwdTranslated = cwdTranslated[0].toLowerCase() + cwdTranslated.substring(1); working = working.replaceAll(cwdTranslated, "~"); } // replace absolute path working = working.replaceAll(this.cwdAbsolute, "~"); // replace relative path working = working.replaceAll(this.cwd, "~"); return working; } /** * Create a copy of this object with output safe for snapshotting. * @param {...function(string):string[]} sanitizeFns functions to apply to the output. * These apply after the default sanitize functions. * @returns {CommandOutput} */ sanitized(...sanitizeFns) { let sani = (s) => sanitizeFns.reduce((s1, f) => f.call({}, s1), this.sanitizeString(s)); return new CommandOutput({ stdout: sani(this.stdout), stderr: sani(this.stderr), }, this.cwd,); } } class TestContext { /** * @param {string} cwd Current Working Directory, used for all commands. */ constructor(cwd) { this.cwd = cwd; this.easkCommand = process.env.EASK_COMMAND || "eask"; this.controller = new AbortController(); } /** * Runs command with "eask" prefix. * See https://nodejs.org/docs/latest-v20.x/api/child_process.html#child_processexeccommand-options-callback * for additional config options. * @param {string} command * @param {any} config * @returns {Promise.} */ runEask(command, config) { return this.run(this.easkCommand + " " + command, config); } /** * Runs a command in the context's directory. * Prefer using Node APIs for commands like copy, delete etc. * See https://nodejs.org/docs/latest-v20.x/api/child_process.html#child_processexeccommand-options-callback * for additional config options. * @param {string} command * @param {any} config * @returns {Promise.} */ run(command, config) { return exec(command, { cwd: this.cwd, signal: this.controller.signal, timeout: getTimeout(), ...config, }) .then((obj) => { if (process.env.DEBUG) { console.log( `--> ${command}\n` + (obj.stdout ? obj.stdout : "") + (obj.stderr ? obj.stderr : ""), ); } return new CommandOutput(obj, this.cwd); }) .catch((err) => { if (!err.code) err.message += "\nexec: TIMEOUT"; throw err; }); } /** * Abort any processes created using `runEask` or `run`. * @returns {void} */ cleanUp() { this.controller.abort(); } /** * Test if a file exists using a path relative to this context's directory. * Throws if file does not exist, to provide an explanation when used with * expect(). * @param {string} relativePath * @returns {Promise.} */ fileExists(relativePath) { const fullPath = path.resolve(this.cwd, relativePath); return fs .stat(fullPath) .then((_) => true) .catch((_) => { throw Error("File does not exist, or is not readable: " + fullPath); }); } /** * Get file contents as a string, relative to the context's directory. * @param {string} relativePath * @returns {Promise.} */ fileContents(relativePath) { const fullPath = path.resolve(this.cwd, relativePath); return fs.readFile(fullPath); } /** * Removes files or directories under the context's directory. * Errors are silently ignored. * @param {...string} filesOrDirs files or directories to remove * @returns {Promise.} */ async removeFiles(...filesOrDirs) { for (let f of filesOrDirs) { await fs .rm(path.join(this.cwd, f), { recursive: true, retries: 1 }) .catch(() => {}); // ignore "does not exist errors" } } errorToCommandOutput(e) { return new CommandOutput(e, this.cwd); } } module.exports = { testUnsafe, emacsVersion, TestContext, getTimeout, CommandOutput, }; ================================================ FILE: test/jest/helpers.test.js ================================================ const { CommandOutput } = require("./helpers"); const path = require("node:path"); describe("CommandOutput", () => { describe("sanitizeString", () => { let out = new CommandOutput({}, "./test/jest"); it("replaces relative paths", () => { let res = out.sanitizeString("look at ./test/jest/foo.js"); expect(res).toBe("look at ~/foo.js"); }); it("replaces relative path literally", () => { let res = out.sanitizeString("look at a/test/jest/foo.js"); expect(res).toBe("look at a/test/jest/foo.js"); }); it("replaces absolute paths", () => { let res = out.sanitizeString( `look at ${path.resolve("./test/jest", "foo.js")}`, ); expect(res).toBe(`look at ${path.join("~", "foo.js")}`); }); it("replaces windows formatted paths", () => { let out = new CommandOutput({}, "./test/jest/options"); // force a windows style path out.cwdAbsolute = "D:\\a\\cli\\cli\\test/jest\\options"; // when on windows Eask translates those paths like let res = out.sanitizeString( "d:/a/cli/cli/test/jest/options/Eask:7:18 Error: Multiple definition of `package'", ); expect(res).toBe("~/Eask:7:18 Error: Multiple definition of `package'"); }); }); }); ================================================ FILE: test/jest/install/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "mini.pkg.1" "0.0.1" "Minimal test package") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1") (keywords "test" "local") (package-file "mini.pkg.1.el") (files "files/*.el") (script "test" "echo \"Have a nice day!~ ;)\"") (script "extra" "echo :") (script "info" "eask info") (eask-defcommand mini-test-1 "Test command 1." (message "Test 1")) (eask-defcommand mini-test-2 "Test command 2." (message "Test 2")) (eask-defcommand mini-test-3 "Test command 3." (message "Test 3: %s" eask-rest)) (source 'gnu) (source 'melpa) ;; (source 'jcs-elpa) (depends-on "emacs" "26.1") (depends-on "f") (depends-on "s") (depends-on "fringe-helper") ;; File install (depends-on "mini.pkg.2" :file "./mini.pkg.2") ;; VC install (when (version<= "29.1" emacs-version) ;; XXX: This takes too long to test, disable for now. ;;(depends-on "msgu" :vc "https://github.com/jcs-elpa/msgu") ) ;; Try out (depends-on "indent-control" :try) (depends-on "auto-rename-tag" :try "https://raw.githubusercontent.com/emacs-vs/auto-rename-tag/refs/heads/master/auto-rename-tag.el") ;; Recipe install (depends-on "watch-cursor" :repo "jcs-elpa/watch-cursor" :fetcher 'github) ;; (depends-on "organize-imports-java" ;; :repo "jcs-elpa/organize-imports-java" ;; :fetcher 'github ;; :files '(:defaults "sdk" "default")) (development (depends-on "ert-runner")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/install/RADME.md ================================================ Minimal Emacs package to simulate development environment; only for testing purposes! ================================================ FILE: test/jest/install/files/mini.pkg.1-1.el ================================================ ;;; mini.pkg.1-1.el --- Extern file 1 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-1.el ;; ;;; Code: (defun mini.pkg.1-1 () "Test function 1." (interactive) ) (provide 'mini.pkg.1-1) ;;; mini.pkg.1-1.el ends here ================================================ FILE: test/jest/install/files/mini.pkg.1-2.el ================================================ ;;; mini.pkg.1-2.el --- Extern file 2 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-2.el ;; ;;; Code: (defun mini.pkg.1-2 () "Test function 2." (interactive) ) (provide 'mini.pkg.1-2) ;;; mini.pkg.1-2.el ends here ================================================ FILE: test/jest/install/foo-mode/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "foo-mode" "0.0.1" "") (website-url "") (keywords ) (package-file "foo-mode.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ================================================ FILE: test/jest/install/foo-mode/foo-mode.el ================================================ ;;; foo-mode.el --- foo mode -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; Author: none ;; Maintainer: none ;; URL: https://github.com/emacs-eask/cli/foo-mode ;; Version: 0.0.1 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: test ;; This file is not part of GNU Emacs. ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; commentary ;; ;;; Code: (defun foo-mode-message () "docstring" (interactive "P") (message "Hello World!")) (provide 'foo-mode) ;;; foo-mode.el ends here ================================================ FILE: test/jest/install/foo-no/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "foo-no" "0.0.1" "") (website-url "") (keywords ) (package-file "foo-no.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ================================================ FILE: test/jest/install/foo-no/foo-no.el ================================================ ;;; foo-no.el --- foo -*- lexical-binding: t -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; Author: none ;; Maintainer: none ;; URL: https://github.com/emacs-eask/cli/foo-no ;; Version: 0.0.1 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: test ;; This file is not part of GNU Emacs ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; commentary ;; ;;; Code: (defun foo-no-message () "docstring" (interactive "P") (message "Hello World!")) (provide 'foo-no) ;;; foo-no.el ends here ================================================ FILE: test/jest/install/mini.pkg.1.el ================================================ ;;; mini.pkg.1.el --- Minimal test package -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1 ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test local ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.1) ;;; mini.pkg.1.el ends here ================================================ FILE: test/jest/install/mini.pkg.2/mini.pkg.2.el ================================================ ;;; mini.pkg.2.el --- Minimal test package 2 -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.2 ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test local ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.2) ;;; mini.pkg.2.el ends here ================================================ FILE: test/jest/install.test.js ================================================ const cmp = require('semver-compare'); const { emacsVersion, TestContext } = require("./helpers"); describe("install and uninstall", () => { describe("in ./install", () => { const ctx = new TestContext("./test/jest/install/"); const packageName = "mini.pkg.1"; beforeAll(async () => { await ctx.runEask("clean all"); }); afterAll(() => ctx.cleanUp()); it("installs project package", async () => { // creates dist/.tar await ctx.runEask("package"); // installs dependencies and generated package await ctx.runEask("install"); const { stderr } = await ctx.runEask("list"); expect(stderr).toMatch(packageName); }); it("installs specific packages", async () => { const { stderr } = await ctx.runEask( "install beacon company-fuzzy transwin", ); expect(stderr).toMatch("beacon"); expect(stderr).toMatch("company-fuzzy"); expect(stderr).toMatch("transwin"); }); it("uninstalls specific packages", async () => { await ctx.runEask("install beacon transwin"); await ctx.runEask("uninstall beacon transwin"); const { stderr } = await ctx.runEask("list"); expect(stderr).not.toMatch("beacon"); expect(stderr).not.toMatch("transwin"); }); it("uninstalls project package", async () => { await ctx.runEask("uninstall"); const { stderr } = await ctx.runEask("list"); expect(stderr).not.toMatch(packageName); }); it("installs dependencies", async () => { const { stderr } = await ctx.runEask("install-deps"); expect(stderr).not.toMatch(packageName); }); it("installs dev dependencies", async () => { const { stderr } = await ctx.runEask("install-deps --dev"); expect(stderr).not.toMatch(packageName); }); /* File install */ describe("eask install-file", () => { beforeAll(async () => { await ctx.runEask("clean workspace"); }); it("installs file directly", async () => { const { stderr } = await ctx.runEask("install-file ./mini.pkg.2"); expect(stderr).toMatch("mini.pkg.2"); }); it("uses the correct package name", async () => { const { stderr } = await ctx.runEask("install-file ./foo-mode"); expect(stderr).toMatch("foo"); }); it("can repeat installs", async () => { await ctx.runEask("install-file ./foo-mode"); }); it("reinstalls a package using --force", async () => { const { stderr } = await ctx.runEask("install-file --force ./foo-mode"); expect(stderr).toMatch("foo"); }); it("installs a package with only an Eask file", async () => { await ctx.runEask("install-file ./foo-no"); }); it("errors when path is non-existing", async () => { await expect(ctx.runEask("install-file ./foo")).rejects.toThrow(); }); it("errors when path is an empty directory", async () => { await expect(ctx.runEask("install-file ../empty")).rejects.toThrow(); }); it("can install tar files created with eask package", async () => { // foo-0.0.1.tar is created by running eask package in ./foo-no await ctx.runEask("install-file ./foo-0.0.1.tar"); }); /* VC install */ test.skip("installs vc directly", async () => { if (cmp(await emacsVersion(), "29.1") == 1) { const { stderr } = await ctx.runEask( "install-vc https://github.com/jcs-elpa/msgu" ); expect(stderr).toMatch("msgu"); } }); }); }); describe("in an empty project", () => { const ctx = new TestContext("./test/jest/empty"); beforeAll(async () => await ctx.runEask("clean all")); afterAll(() => ctx.cleanUp()); it("errors on install", async () => { await expect(ctx.runEask("install")).rejects.toThrow(); }); it("errors on uninstall", async () => { await expect(ctx.runEask("uninstall")).rejects.toThrow(); }); it("errors on reinstall", async () => { await expect(ctx.runEask("reinstall")).rejects.toThrow(); }); }); }); ================================================ FILE: test/jest/link/.gitkeep ================================================ ================================================ FILE: test/jest/link/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "link" "1.0.0" "") (website-url "") (keywords ) (package-file "link.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ================================================ FILE: test/jest/link/link-fail/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "mini.pkg.1" "0.0.1" "Minimal test package") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1") (keywords "test" "local") (package-file "mini.pkg.1.el") (files "files/*.el") (script "test" "echo \"Have a nice day!~ ;)\"") (script "extra" "echo :") (script "info" "eask info") (eask-defcommand mini-test-1 "Test command 1." (message "Test 1")) (eask-defcommand mini-test-2 "Test command 2." (message "Test 2")) (eask-defcommand mini-test-3 "Test command 3." (message "Test 3: %s" eask-rest)) (source 'gnu) (source 'melpa) (source 'jcs-elpa) (depends-on "emacs" "26.1") (depends-on "f") (depends-on "s") (depends-on "fringe-helper") (depends-on "watch-cursor" :repo "jcs-elpa/watch-cursor" :fetcher 'github) (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/link/link-fail/RADME.md ================================================ Minimal Emacs package to simulate development environment; only for testing purposes! ================================================ FILE: test/jest/link/link-fail/files/mini.pkg.1-1.el ================================================ ;;; mini.pkg.1-1.el --- Extern file 1 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-1.el ;; ;;; Code: (defun mini.pkg.1-1 () "Test function 1." (interactive) ) (provide 'mini.pkg.1-1) ;;; mini.pkg.1-1.el ends here ================================================ FILE: test/jest/link/link-fail/files/mini.pkg.1-2.el ================================================ ;;; mini.pkg.1-2.el --- Extern file 2 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-2.el ;; ;;; Code: (defun mini.pkg.1-2 () "Test function 2." (interactive) ) (provide 'mini.pkg.1-2) ;;; mini.pkg.1-2.el ends here ================================================ FILE: test/jest/link/link-fail/mini.pkg.1.el ================================================ ;;; mini.pkg.1.el --- Minimal test package -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1 ;; Version: 0.0.1 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test local ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.1) ;;; mini.pkg.1.el ends here ================================================ FILE: test/jest/link/link-to/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "link-to" "1.0.0" "simple package to link") (website-url "https://example.org") (keywords "test") (package-file "link-to.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") (depends-on "json-mode") ================================================ FILE: test/jest/link/link-to/link-to.el ================================================ ;;; link-to.el --- summary -*- lexical-binding: t -*- ;; Version: 1.0.0 ;; Package-Requires: ((emacs "26.2") (json-mode)) ;; Keywords: test ;; This file is not part of GNU Emacs ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; commentary ;;; Code: (message "Hello World!") (provide 'link-to) ;;; link-to.el ends here ================================================ FILE: test/jest/link.test.js ================================================ const { TestContext } = require("./helpers"); describe("link", () => { describe("in ./link", () => { const ctx = new TestContext("./test/jest/link"); beforeAll(async () => { await ctx.runEask("clean workspace"); await ctx.runEask("refresh"); }); // check list command first since it is used in later tests it("eask link list", async () => { await ctx.runEask("link list"); }); const linkName = "link-to"; const linkPath = "./link-to/"; describe("eask link add", () => { let addResult; // undefined if the first test fails it("adds a link", async () => { addResult = await ctx.runEask(`link add "${linkName}" "${linkPath}"`); expect(addResult.stderr).toMatch("Created link"); }); it("should show the new link", async () => { expect(addResult).toBeTruthy(); // fail if previous test failed const { stdout, stderr } = await ctx.runEask("link list"); // the name of the linked package should be included // FIXME: Some output is on stderr, some is on stdout const output = stderr + "/n" + stdout; expect(output).toMatch(linkName); }); // side effects of linking it(`installs dependencies of ${linkName}`, async () => { const { stderr } = await ctx.runEask("list"); expect(stderr).toMatch("json-mode"); }); it("generates a pkg.el in linked package", async () => { expect(await ctx.fileExists(`${linkPath}/${linkName}-pkg.el`)).toBe( true, ); }); it("can add the same link again", async () => { await ctx.runEask(`link add "${linkName}" "${linkPath}"`); }); // FIXME: bug // - fails when there is a pkg-file in link-fail // - succeeds when no pkg-file // this means: 1st run it adds link-fail, generates new pkg.el // 2nd run it fails to add link-fail! it.skip("fails to add a link to uninstallable package", async () => { // mini.pkg.1 contains dependencies from melpa, but this package only has gnu await expect( ctx.runEask('link add "mini.pkg.1" "./link-fail/"'), ).rejects.toMatchObject({ stderr: expect.stringContaining("Package not installable"), }); }); }); describe("eask link delete", () => { beforeAll(async () => { await ctx.runEask("clean workspace"); // this was tested in a previous command // redo it here so that this test will fail if no link was added await ctx.runEask(`link add "${linkName}" "${linkPath}"`); }); it("deletes an added link", async () => { const { stderr } = await ctx.runEask(`link delete ${linkName}-1.0.0`); expect(stderr).toMatch("Package `link-to-1.0.0` unlinked"); }); it("should show no links", async () => { const { stderr } = await ctx.runEask("link list"); expect(stderr).toMatch("No linked packages"); }); }); }); describe("in an empty directory", () => { const ctx = new TestContext("./test/jest/empty"); beforeAll(async () => { await ctx.runEask("clean all"); }); it("eask link list", async () => { const { stderr } = await ctx.runEask("link list"); expect(stderr).toMatch("No linked packages"); }); describe("eask link add", () => { it("should error when called with no arguments", async () => { await expect(ctx.runEask("link add")).rejects.toMatchObject({ code: 1, }); }); it("should error when linking a folder with no package file", async () => { await expect(ctx.runEask("link add foo .")).rejects.toThrow(); }); // FIXME: update this test when this bug is fixed test.failing( "should error when linking a non-existing folder", async () => { // currently this prints an error message but does not exit with a code await expect( ctx.runEask('link add bar "./nothing"'), ).rejects.toThrow(); }, ); }); describe("eask link delete", () => { it("should error when called with no arguments", async () => { await expect(ctx.runEask("link delete")).rejects.toThrow(); }); it("should do nothing when deleting an unknown package", async () => { const { stderr } = await ctx.runEask("link delete foo"); expect(stderr).toMatch("No links present"); }); }); }); }); ================================================ FILE: test/jest/lint/Eask ================================================ (package "lint" "0.0.1" "Test project for lint") (package-file "lint-pkg.el") (source 'gnu) (source 'melpa) ================================================ FILE: test/jest/lint/checkdoc-fail.el ================================================ ;;; checkdoc-fail.el --- Test checkdoc fail -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command ert ;;; Code: (defun my-foo (arg) ".make a lot of checkdoc errors in this " ignore) ;;; checkdoc-fail.el ends here ================================================ FILE: test/jest/lint/declare-fail.el ================================================ ;;; declare-fail.el --- Test declare-fail -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;;; Code: (declare-function not-a-function "subr.el" (x y z)) ;;; declare-fail.el ends here ================================================ FILE: test/jest/lint/declare-ok.el ================================================ ;;; declare-ok.el --- Test declare-ok -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for the command ert ;;; Code: (declare-function ignore "subr.el" (&rest args)) ;;; declare-ok.el ends here ================================================ FILE: test/jest/lint/elisp-lint-ok.el ================================================ ;;; elisp-lint-ok.el --- Test elisp-lint-ok -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;;; Version: 0.0.1 ;;; URL: https://foo.com ;;; Package-Requires: ((emacs "28.1")) ;;; Commentary: ;; Tests linting with elisp-lint ;; Also clean for packge lint ;;; Code: (defun elisp-lint-ok-foo () "Nothing here." (message "ok")) (provide 'elisp-lint-ok) ;;; elisp-lint-ok.el ends here ================================================ FILE: test/jest/lint/elsa-warn.el ================================================ ;;; elsa-warn.el --- Test elsa-warn -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Test elsa. ;; Should only cause warnings ;;; Code: (defun elsa-warn-foo () (some-foo) ;; not declared warning (message "Ok")) ;;; elsa-warn.el ends here ================================================ FILE: test/jest/lint/indent-warn.el ================================================ ;;; indent-warn.el --- Test indent-warn -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Test indent. ;; Should only cause warnings ;;; Code: (defun indent-warn-foo () (if 't (message "Ok") (message "no"))) ;;; indent-warn.el ends here ================================================ FILE: test/jest/lint/keywords-bad/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "keywords-ok" "1.0.0" "") (keywords "bag" "frog") (package-file "keywords-ok.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ================================================ FILE: test/jest/lint/keywords-bad/keywords-ok.el ================================================ ;;; keywords-ok.el --- summary -*- lexical-binding: t -*- ;; Author: ;; Maintainer: ;; Version: 1.0.0 ;; Package-Requires: (dependencies) ;; Keywords: frog, bag ;; This file is not part of GNU Emacs ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; commentary ;;; Code: (message "Hello World!") (provide 'keywords-ok) ;;; keywords-ok.el ends here ================================================ FILE: test/jest/lint/keywords-ok/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "keywords-ok" "1.0.0" "") (keywords "tools") (package-file "keywords-ok.el") (script "test" "echo \"Error: no test specified\" && exit 1") (source "gnu") (depends-on "emacs" "26.1") ================================================ FILE: test/jest/lint/keywords-ok/keywords-ok.el ================================================ ;;; keywords-ok.el --- summary -*- lexical-binding: t -*- ;; Author: ;; Maintainer: ;; Version: version ;; Package-Requires: (dependencies) ;; Keywords: tools ;; This file is not part of GNU Emacs ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; commentary ;;; Code: (message "Hello World!") (provide 'keywords-ok) ;;; keywords-ok.el ends here ================================================ FILE: test/jest/lint/regexp-warn.el ================================================ ;;; regexp-warn.el --- Test regexp-warn -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Test regexp. ;; Should only cause warnings ;;; Code: (defun regexp-warn-foo () (re-search-backward "^[.:wrod:]$")) ;;; regexp-warn.el ends here ================================================ FILE: test/jest/lint.test.js ================================================ const { TestContext } = require("./helpers"); describe("lint", () => { const ctx = new TestContext("./test/jest/lint"); beforeEach(async () => { await ctx.runEask("clean elc"); }); afterAll(() => { ctx.cleanUp(); }); describe("partial input", () => { it("eask lint should error", async () => { await expect(ctx.runEask("eask lint")).rejects.toMatchObject({ code: 1 }); }); }); describe("checkdoc", () => { it("should work on declare-ok.el", async () => { await expect( ctx.runEask("lint checkdoc declare-ok.el"), ).resolves.toMatchObject({ stderr: expect.stringContaining("No issues found"), }); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint checkdoc --strict checkdoc-fail.el"), ).rejects.toMatchObject({ code: 1, }); }); it("should error given --strict and --allow-error", async () => { await expect( ctx.runEask("lint checkdoc --strict checkdoc-fail.el declare-ok.el"), ).rejects.toMatchObject({ code: 1, // expect that declare-ok was checked too stderr: expect.stringContaining("`declare-ok.el` with checkdoc"), }); }); }); describe("declare", () => { it("should work on declare-ok.el", async () => { await expect( ctx.runEask("lint declare declare-ok.el"), ).resolves.toMatchObject({ stderr: expect.stringContaining("No issues found"), }); }); it("should work on declare-fail.el (warnings ignored)", async () => { await ctx.runEask("lint declare declare-fail.el"); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint declare --strict declare-fail.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should error given --strict --allow-error", async () => { await expect( ctx.runEask("lint declare --strict --allow-error ./*.el"), ).rejects.toMatchObject({ code: 1, // expect that declare-ok was checked too stderr: expect.stringContaining("`declare-ok.el` with check-declare"), }); }); }); describe("elint", () => { it("should work on declare-ok.el", async () => { await ctx.runEask("lint elint declare-ok.el"); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint elint --strict checkdoc-fail.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should check both files given --strict --allow-error", async () => { await expect( ctx.runEask( "lint elint --strict --allow-error checkdoc-fail.el declare-ok.el", ), ).rejects.toMatchObject({ code: 1, stderr: expect.stringContaining("`declare-ok.el` with elint"), }); }); }); describe("elisp-lint", () => { it("should work on elisp-lint-ok.el", async () => { await ctx.runEask("lint elisp-lint --strict elisp-lint-ok.el"); }); it("should work on declare-ok.el (warnings)", async () => { await ctx.runEask("lint elisp-lint declare-ok.el"); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint elisp-lint --strict declare-ok.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should work with --strict --allow-error", async () => { await expect( ctx.runEask( "lint elisp-lint --strict --allow-error checkdoc-fail.el elisp-lint-ok.el", ), ).rejects.toMatchObject({ stderr: expect.stringContaining("2 files linted"), }); }); }); describe("elsa", () => { // prints a lot of garbage // likely an upstream problem, unclear how to fix it("should work on elisp-lint-ok.el", async () => { await ctx.runEask("lint elsa elisp-lint-ok.el"); }); it("should work on elsa-warn.el (warnings)", async () => { await ctx.runEask("lint elsa elsa-warn.el"); }); it("should error on declare-ok.el", async () => { await expect( ctx.runEask("lint elsa declare-ok.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint elsa --strict elsa-warn.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should run all files given --allow-error", async () => { await expect( ctx.runEask( "lint elsa --strict --allow-errors elsa-warn.el elisp-lint-ok.el", ), ).rejects.toMatchObject({ code: 1 }); }); }); describe("indent", () => { it("should work on indent-warn.el", async () => { await ctx.runEask("lint indent indent-warn.el"); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint indent --strict indent-warn.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should work with --strict --allow-error", async () => { await expect( ctx.runEask( "lint indent --strict --allow-error indent-warn.el declare-ok.el", ), ).rejects.toMatchObject({ code: 1, stderr: expect.stringContaining("2 files linted"), }); }); }); describe("keywords", () => { // always errors so --strict doesn't matter // checks only Eask and x-pkg.el, so --allow-error doesn't matter it("should error in an empty project", async () => { const ctxEmpty = new TestContext("./test/jest/lint"); await expect(ctxEmpty.runEask("lint keywords")).rejects.toThrow(); }); it("should work in keywords-ok", async () => { const ctxOk = new TestContext("./test/jest/lint/keywords-ok"); await expect(ctxOk.runEask("lint keywords")).resolves.toMatchObject({ stderr: expect.stringContaining("No issues found"), }); }); it("should error in keywords-bad", async () => { const ctxBad = new TestContext("./test/jest/lint/keywords-bad"); await expect(ctxBad.runEask("lint keywords")).rejects.toMatchObject({ stderr: expect.stringContaining("Missing a standard keyword"), }); }); }); describe("package", () => { it("should work on declare-ok.el", async () => { await ctx.runEask("lint package declare-ok.el"); }); it("should error given --strict", async () => { await expect( ctx.runEask("lint package --strict declare-ok.el"), ).rejects.toMatchObject({ code: 1 }); }); // note that all files are checked anyway so --allow-error has no effect it("should check all files given --allow-error", async () => { await expect( ctx.runEask("lint package --allow-error *.el"), ).rejects.toMatchObject({ code: 1, stderr: expect.stringContaining("`declare-ok.el` with package-lint"), }); }); }); describe("regexps", () => { it("should work on declare-ok.el", async () => { await ctx.runEask("lint regexps declare-ok.el"); }); it("should have warnings on regexp-warn.el", async () => { await expect( ctx.runEask("lint regexps regexp-warn.el"), ).resolves.toMatchObject({ stderr: expect.stringContaining("Warning:") }); }); it("should error on regexp-warn.el with --strict", async () => { await expect( ctx.runEask("lint regexps --strict regexp-warn.el"), ).rejects.toMatchObject({ code: 1 }); }); it("should work with --allow-error", async () => { await expect( ctx.runEask( "lint regexps --allow-error --strict regexp-warn.el declare-ok.el", ), ).rejects.toMatchObject({ code: 1, stderr: expect.stringContaining("2 files linted"), }); }); }); }); ================================================ FILE: test/jest/local/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el # Documentation generation tests docs/ ================================================ FILE: test/jest/local/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "mini.pkg.1" "12.12.13" "Minimal test package") (website-url "https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1") (keywords "test" "local") (package-file "mini.pkg.1.el") (files "files/*.el") (script "test" "echo \"Have a nice day!~ ;)\"") (script "extra" "echo :") (script "info" "eask info") (eask-defcommand mini-test-1 "Test command 1." (message "Test 1")) (eask-defcommand mini-test-2 "Test command 2." (message "Test 2")) (eask-defcommand mini-test-3 "Test command 3." (message "Test 3: %s" eask-rest)) (source 'gnu) (source 'melpa) (source 'jcs-elpa) (depends-on "emacs" "26.1") (depends-on "f") (depends-on "s") (depends-on "fringe-helper") ;; FIXME: including these takes a really long time ;; perhaps move to a dedicated test file (depends-on "watch-cursor" :repo "jcs-elpa/watch-cursor" :fetcher 'github) (depends-on "organize-imports-java" :repo "jcs-elpa/organize-imports-java" :fetcher 'github :files '(:defaults "sdk" "default")) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/local/RADME.md ================================================ Minimal Emacs package to simulate development environment; only for testing purposes! ================================================ FILE: test/jest/local/README.org ================================================ * Test Org File This is a test org file used for Emacs behavior checking. ** TODO Broken heading without space ***BROKEN Heading ** Code Block #+BEGIN_SRC emacs-lisp (message "Hello, Emacs!") (1+ ) ; Intentional syntax error #+END_SRC ** Bad Property :PROPERTIES: :WRONGPROP without value :END: ** Notes Unclosed *bold and /italic text. ================================================ FILE: test/jest/local/files/mini.pkg.1-1.el ================================================ ;;; mini.pkg.1-1.el --- Extern file 1 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-1.el ;; ;;; Code: (defun mini.pkg.1-1 () "Test function 1." (interactive)) (provide 'mini.pkg.1-1) ;;; mini.pkg.1-1.el ends here ================================================ FILE: test/jest/local/files/mini.pkg.1-2.el ================================================ ;;; mini.pkg.1-2.el --- Extern file 2 -*- lexical-binding: t; -*- ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; files/mini.pkg.1-2.el ;; ;;; Code: (defun mini.pkg.1-2 () "Test function 2." (interactive)) (provide 'mini.pkg.1-2) ;;; mini.pkg.1-2.el ends here ================================================ FILE: test/jest/local/mini.pkg.1.el ================================================ ;;; mini.pkg.1.el --- Minimal test package -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; Created date 2022-03-29 01:52:58 ;; Author: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/cli/tree/master/test/fixtures/mini.pkg.1 ;; Version: 12.12.13 ;; Package-Requires: ((emacs "24.3") (s "1.12.0") (fringe-helper "1.0.1")) ;; Keywords: test local ;; This file is NOT part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Minimal Emacs package to simulate development environment; only for testing ;; purposes! ;; ;;; Code: (require 's) (require 'fringe-helper) (provide 'mini.pkg.1) ;;; mini.pkg.1.el ends here ================================================ FILE: test/jest/local.test.js ================================================ // Here we test all local (workspace) commands by simulating a Emacs // pacakge development environment! // Notice, below we clone a random package (repo) that uses Eask as the // dependencies management tool. const cmp = require('semver-compare'); const { emacsVersion, TestContext } = require("./helpers"); describe("local", () => { const cwd = "./test/jest/local"; const ctx = new TestContext(cwd); // NOTE: `install-deps` takes a long time in this package // this is because of recipe dependencies triggering // "temporary archives" build. beforeAll(async () => { await ctx.runEask("install-deps", { timeout: 40000 }); }); afterAll(() => ctx.cleanUp()); describe("info commands", () => { it("info", async () => { await ctx.runEask("info"); }); it("status", async () => { await ctx.runEask("status"); }); it("archives", async () => { await ctx.runEask("archives"); }); it("archives --all", async () => { await ctx.runEask("archives --all"); }); it("list --depth=0", async () => { await ctx.runEask("list --depth=0"); }); // this alters the eask file it("bump", async () => { await ctx.runEask("bump major minor patch"); }); // NOTE: eask cat is a long running command it("cat", async () => { // cat requires e2ansi, installing it takes a while await ctx.runEask("cat package.json --insecure"); await ctx.runEask("cat package.json --number --insecure"); }); it("concat", async () => { await ctx.runEask("concat"); }); // NOTE: eask loc is a long running command it("loc", async () => { // installs markdown mode -- depends on emacs 28.1 if (cmp(await emacsVersion(), "28.1") == 1) { await ctx.runEask("loc"); await ctx.runEask("loc Eask"); } }); }); describe("PATH environment", () => { it("path", async () => { await ctx.runEask("path"); await ctx.runEask("path bin"); }); it("load-path", async () => { await ctx.runEask("load-path"); await ctx.runEask("load-path bin"); }); }); describe("Preparation", () => { beforeAll(async () => await ctx.runEask("prepare --dev")); afterAll(async () => await ctx.runEask("clean dist")); it("package", async () => { await ctx.runEask("package"); }); }); describe("Documentation", () => { it("docs el2org", async () => { if (cmp(await emacsVersion(), "28.1") == 1) { await ctx.runEask("docs el2org"); } }); }); describe("Development", () => { beforeAll(async () => { await ctx.runEask("install-deps", { timeout: 40000 }); }); // this requires install-deps it("compile", async () => { await ctx.runEask("compile"); await ctx.runEask("compile --clean"); await ctx.runEask("recompile"); await ctx.runEask("recompile --clean"); }); it("recipe", async () => { await ctx.runEask("recipe"); }); it("keywords", async () => { await ctx.runEask("keywords"); }); it("run script", async () => { await ctx.runEask("run script"); await ctx.runEask("run script test"); await ctx.runEask("run script extra -- Extra arguments!"); await ctx.runEask("run script --all"); }); it("run command", async () => { await ctx.runEask("run command"); await ctx.runEask("run command test"); await ctx.runEask("run command mini-test-3 -- Extra arguments!"); await ctx.runEask("run command --all"); }); }); describe("Execution", () => { beforeAll(async () => { await ctx.runEask("install-deps", { timeout: 40000 }); }); test("eval", async () => { await ctx.runEask('eval "(progn (require \'mini.pkg.1))"'); }); }); describe("Generating", () => { beforeAll(async () => { // from generate ignore elisp await ctx.removeFiles(".gitignore"); }); afterAll(async () => { await ctx.runEask("clean autoloads").catch(() => {}); await ctx.runEask("clean pkg-file").catch(() => {}); await ctx.removeFiles( "recipes", // from generate recipes ".gitignore", // from generate ignore elisp ); // restore original .gitignore await ctx.run("git restore ."); }); it.each([ "generate autoloads", "generate pkg-file", "generate recipe -y", "generate ignore elisp", ])("eask %s", async (cmd) => { await ctx.runEask(cmd); }); it.skip("eask generate license", async () => { // XXX: Avoid API rate limit exceeded error await ctx.runEask("generate license gpl-3.0"); }); }); describe("Generating tests", () => { // some tests fail if ./test already exists beforeEach(async () => await ctx.removeFiles("features", "test", "tests")); afterAll(async () => await ctx.removeFiles("features", "test", "tests")); it.each([ "generate test ert", "generate test ert-runner", "generate test buttercup", "generate test ecukes", ])("eask %s", async (cmd) => { await ctx.runEask(cmd); }); }); describe("Generating workflow", () => { beforeEach( async () => await ctx.removeFiles( ".circleci", ".gitlab-ci.yml", ".github", ".travis.yml", ), ); afterAll( async () => await ctx.removeFiles( ".circleci", ".gitlab-ci.yml", ".github", ".travis.yml", ), ); it.each([ "generate workflow circle-ci", "generate workflow github", "generate workflow gitlab", "generate workflow travis-ci", ])("eask %s", async (cmd) => { await ctx.runEask(cmd); }); }); describe("Linting", () => { // some lint commands may fail if packages are missing beforeAll(async () => { await ctx.runEask("install-deps", { timeout: 40000 }); }); it.each([ "lint checkdoc", "lint declare", "lint elint", "lint elisp-lint", "lint indent", "lint keywords", "lint license", "lint package", ])("eask %s", async (cmd) => { await ctx.runEask(cmd); }); // XXX: Elsa is not stable, ignore it for now test.skip("lint elsa", async () => { await ctx.runEask("lint elsa"); }); it("lint regexps", async () => { if (cmp(await emacsVersion(), "27.1") == 1) { await ctx.runEask("lint regexps"); } }); it("lint org *.org", async () => { await ctx.runEask("lint org *.org"); }); }); describe("Testing", () => { it("test activate", async () => { await ctx.runEask("test activate"); }); }); describe("Formatting", () => { // installs elisp-autofmt it("format elisp-autofmt", async () => { if (cmp(await emacsVersion(), "29.1") == 1) { await ctx.runEask("format elisp-autofmt"); } }); it("format elfmt", async () => { await ctx.runEask("format elfmt"); }); }); describe("Cleaning", () => { it.each([ "clean .eask", "clean elc", "clean dist", "clean autoloads", "clean pkg-file", "clean log-file", "clean all", ])("eask %s", async (cmd) => { await ctx.runEask(cmd); }); }); describe("Control DSL", () => { it("source add", async () => { await ctx.runEask('source add test "https://test.elpa.com"'); }); it("source delete test", async () => { await ctx.runEask("source delete test"); }); it("source list", async () => { await ctx.runEask("source list"); }); }); describe("Util", () => { it("locate", async () => { await ctx.runEask("locate"); }); it("refresh", async () => { await ctx.runEask("refresh"); }); }); }); ================================================ FILE: test/jest/options/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/options/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "options" "0.0.0" "Test all options flag") (website-url "https://github.com/emacs-eask/options") (keywords "test") (package-file "options.el") (source 'gnu) (depends-on "emacs" "26.1") ================================================ FILE: test/jest/options/options.el ================================================ ;;; options.el --- Test all options flag -*- lexical-binding: t; -*- ;; Copyright (C) 2025-2026 the Eask authors. ;; Author: Shen, Jen-Chieh ;; Maintainer: Shen, Jen-Chieh ;; URL: https://github.com/emacs-eask/options ;; Version: 0.0.0 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: test ;; This file is not part of GNU Emacs. ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Test options. ;; ;;; Code: ;; Empty.. (provide 'options) ;;; options.el ends here ================================================ FILE: test/jest/options.test.js ================================================ const { TestContext } = require("./helpers"); describe("options", () => { const ctx = new TestContext("./test/jest/options"); describe("eask info errors", () => { const ctx = new TestContext(process.env.HOME); test("eask info should error without an Easkfile", async () => { const hasGlobal = (await ctx.fileExists("Eask").catch(() => false)) || (await ctx.fileExists("Easkfile").catch(() => false)); // If there is a global Easkfile then just skip testing // Better to not test than to raise false errors if (!hasGlobal) { await expect(ctx.runEask("info -g")).rejects.toThrow(); await expect(ctx.runEask("info --global")).rejects.toThrow(); } }); }); describe("verbose option", () => { it("should error if no number is given", async () => { await expect(ctx.runEask("info -v")).rejects.toMatchObject({ code: 1 }); }); it("should error if the number is omitted before the next option", async () => { await expect(ctx.runEask("info -v --no-color")).rejects.toMatchObject({ code: EXIT_FAILURE, }); }); }); test.each([ "-a", "--all", "-q", "--quick", "-f", "--force", "--debug", "--strict", "--allow-error", "--insecure", "--timestamps", "--log-level", "--elapsed-time", "--et", "--no-color", "--proxy localhost:8080", "--http-proxy localhost:8080", "--https-proxy localhost:8080", "--no-proxy localhost:8080", "-v 4", "--verbose 4", ])("eask info %s", async (opt) => { await ctx.runEask(`info ${opt}`); }); }); ================================================ FILE: test/jest/outdated-upgrade/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/outdated-upgrade/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "outdated-upgrade" "0.0.1" "Test project for command outdated and upgrade") (package-file "outdated-upgrade.el") (source 'gnu) (source 'melpa) (depends-on "f") (depends-on "s") (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/outdated-upgrade/make-outdate.el ================================================ ;;; make-outdate.el --- Make some packages outdate -*- lexical-binding: t; -*- ;; Copyright (C) 2022-2026 the Eask authors. ;; This file is not part of GNU Emacs. ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;; Make some packages outdate ;; ;;; Code: (eask-pkg-init) (require 'f) (defconst make-outdate-version "20001122.1234" "Outdate version.") (defun make-outdate-package (name) "Make package (NAME) outdate." (let* ((dir (file-name-directory (locate-library name))) (pkg (concat dir name "-pkg.el"))) (with-current-buffer (find-file pkg) (goto-char (point-min)) (when (re-search-forward "\"[0-9.]*\"" nil t) (save-excursion (let ((end (point))) (backward-sexp) (delete-region (point) end) (insert (format "\"%s\"" make-outdate-version))))) (save-buffer) (kill-current-buffer)) (let ((dest (expand-file-name (concat name "-" make-outdate-version "/") package-user-dir))) (eask-info "Moving %s" dir) (eask-info " to %s" dest) (ignore-errors (make-directory dest t)) (f-copy-contents dir dest) (ignore-errors (delete-directory dir t))))) (make-outdate-package "dash") (make-outdate-package "f") (provide 'make-outdate) ;;; make-outdate.el ends here ================================================ FILE: test/jest/outdated-upgrade.test.js ================================================ const { emacsVersion, TestContext } = require("./helpers"); describe("outdated and upgrade", () => { const ctx = new TestContext("./test/jest/outdated-upgrade"); beforeAll(async () => { await ctx.runEask("install-deps", { timeout: 40000 }); await ctx.runEask("load make-outdate.el"); }); // these will run in sequence afterAll(async () => await ctx.runEask("clean all")); afterAll(() => ctx.cleanUp()); test("list outdated", async () => { await ctx.runEask("outdated"); await ctx.runEask("outdated --depth 0"); }); test("upgrade", async () => { await ctx.runEask("upgrade"); }); }); ================================================ FILE: test/jest/search/.gitignore ================================================ # ignore these directories /.git /recipes # ignore generated files *.elc # eask packages .eask/ dist/ # packaging *-autoloads.el *-pkg.el ================================================ FILE: test/jest/search/Eask ================================================ ;; -*- mode: eask; lexical-binding: t -*- (package "search" "0.0.1" "Test project for command `search'") (package-file "search.el") (source 'gnu) (source 'melpa) (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 ================================================ FILE: test/jest/search.test.js ================================================ const { TestContext } = require("./helpers"); describe("search", () => { const cwd = "./test/jest/search"; const ctx = new TestContext(cwd); test("eask search company", async () => { await ctx.runEask("search company"); }); test("eask search company dash --depth 0", async () => { await ctx.runEask("search company dash --depth 0"); }); test("eask search company dash f s --depth 0 -g", async () => { await ctx.runEask("search company dash f s --depth 0 -g"); }); test("eask search should error", async () => { await expect(ctx.runEask("search")).rejects.toThrow(); }); }); ================================================ FILE: test/jest/test-buttercup.test.js ================================================ const { TestContext } = require("./helpers"); describe("buttercup", () => { const ctx = new TestContext("./test/jest/buttercup"); beforeAll(async () => await ctx.runEask("install-deps --dev")); test("run all tests", async () => { // this runs all tests, so should error await expect(ctx.runEask("test buttercup")).rejects.toThrow(); }); // buttercup takes directories as arguments test("run succeeding tests", async () => { await ctx.runEask("test buttercup ./test-ok"); }); test("run all tests explicitly", async () => { await expect( ctx.runEask("test buttercup ./test-ok ./test-fail"), ).rejects.toThrow(); }); test("does not take options", async () => { await ctx.runEask("test buttercup --no-color ./test-ok"); }); // Because load-path is manually set, cannot refer to parent directories. // Note this does work if you do ../buttercup/test-ok/, but not for any other directory. test("running paths in parent dir", async () => { await expect(ctx.runEask("test buttercup ../bin")).rejects.toThrow(); }); }); ================================================ FILE: test/jest/test-ecukes.test.js ================================================ const { TestContext } = require("./helpers"); describe("test_ecukes", () => { const ctx = new TestContext("./test/jest/ecukes"); beforeAll(async () => await ctx.runEask("install-deps --dev")); afterAll(() => ctx.cleanUp()); test("eask test ecukes", async () => { await ctx.runEask("test ecukes"); }); test("eask test ecukes ./features/foo.feature", async () => { await ctx.runEask("test ecukes ./features/foo.feature"); }); }); ================================================ FILE: test/jest/test-ert-runner.test.js ================================================ const { TestContext } = require("./helpers"); describe("test ert-runner", () => { const ctx = new TestContext("./test/jest/ert-runner"); beforeAll(async () => await ctx.runEask("install-deps --dev")); afterAll(() => ctx.cleanUp()); test("eask test ert-runner ./test/*.el", async () => { await ctx.runEask("test ert-runner ./test/*.el"); }); }); ================================================ FILE: test/jest/test-ert.test.js ================================================ const { TestContext } = require("./helpers"); describe("test-ert", () => { const ctx = new TestContext("./test/jest/ert"); afterAll(() => ctx.cleanUp()); test("eask test ert ./test/*.el", async () => { await ctx.runEask("test ert ./test/*.el"); }); test("nil message", async () => { await ctx.runEask("test ert ./test-nil-message/*.el"); }); test("no files", async () => { await expect(ctx.runEask("test ert")).rejects.toThrow(); }); }); ================================================ FILE: test/jest/upgrade-eask.test.js ================================================ const { testUnsafe, TestContext } = require("./helpers"); describe("upgrade-eask", () => { const ctx = new TestContext(); testUnsafe("eask upgrade-eask", async () => { await ctx.runEask("upgrade-eask"); }); }); ================================================ FILE: webinstall/install.bat ================================================ @echo off :: Copyright (C) 2023-2026 the Eask authors. :: This program is free software; you can redistribute it and/or modify :: it under the terms of the GNU General Public License as published by :: the Free Software Foundation; either version 3, or (at your option) :: any later version. :: This program is distributed in the hope that it will be useful, :: but WITHOUT ANY WARRANTY; without even the implied warranty of :: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the :: GNU General Public License for more details. :: You should have received a copy of the GNU General Public License :: along with this program. If not, see . ::: Commentary: :: :: TODO(everyone): Keep this script simple and easily auditable. :: if "%PROCESSOR_ARCHITECTURE%" == "AMD64" ( set ARCH=x64 ) else if "%PROCESSOR_ARCHITECTURE%" == "ARM64" ( set ARCH=arm64 ) else ( echo Error: Unsupported architecture detected: `%PROCESSOR_ARCHITECTURE%`. exit /b 1 ) set URL=https://github.com/emacs-eask/binaries/raw/master/win-%ARCH%.zip set EASK_BIN_DIR=%USERPROFILE%\.local\bin set ZIP=%EASK_BIN_DIR%\eask.zip mkdir %EASK_BIN_DIR% curl.exe -fsSL %URL% -o %ZIP% tar.exe -xf %ZIP% -C %EASK_BIN_DIR% del %ZIP% echo. echo ✓ Eask is installed in %EASK_BIN_DIR%. echo. echo Don't forget to add %EASK_BIN_DIR% to PATH environment variable: echo. echo set PATH=%EASK_BIN_DIR%;%%PATH%% echo. ================================================ FILE: webinstall/install.sh ================================================ #!/bin/sh # Copyright (C) 2023-2026 the Eask authors. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## Commentary: # # TODO(everyone): Keep this script simple and easily auditable. # set -e if ! command -v unzip >/dev/null; then echo "Error: unzip is required to install Eask." 1>&2 exit 1 fi if [ "$OS" = "Windows_NT" ]; then ext="zip" else ext="tar.gz" fi if [ "$OS" = "Windows_NT" ]; then target="win-x64" else case $(uname -sm) in "Darwin x86_64") target="macos-x64" ;; "Darwin arm64") target="macos-arm64" ;; "Linux aarch64") target="linux-arm64" ;; *) target="linux-x64" ;; esac fi eask_uri="https://github.com/emacs-eask/binaries/raw/master/${target}.${ext}" eask_bin_dir=~/.local/bin dwd_file=$eask_bin_dir/eask.${ext} mkdir -p $eask_bin_dir curl -fsSL $eask_uri -o $dwd_file if [ "$OS" = "Windows_NT" ]; then unzip -d "$eask_bin_dir" -o "$dwd_file" else tar -xvzf "$dwd_file" -C "$eask_bin_dir" fi rm $dwd_file echo echo "✓ Eask is installed in ${eask_bin_dir}." echo echo "Don't forget to add ${eask_bin_dir} to PATH environment variable:" echo echo " export PATH=\"${eask_bin_dir}:\$PATH\"" echo