Hello, friends!
I would like to have the ability to organize and create new project types in some way, like you can with Projectile , as shown in the [Projectile docs](https://docs.projectile.mx/projectile/projects.html#adding-custom-project-types) to create a new project type:
(projectile-register-project-type 'npm '("package.json")
:project-file "package.json"
:compile "npm install"
:test "npm test"
:run "npm start"
:test-suffix ".spec")
Maybe the built-in project and EDE could potentially fill this role, but maybe not. I'm curious about what y'all think. Allow me to share what I found while looking around some of the Emacs source code.
EDE, Emacs Development Environment, has an interesting interactive function `ede-new` which allows you to go through prompts to specify a new project.
However if you do `M-x ede-new` you'll find that there's only 2 project types, Make and Automake. There is also the function `ede-eneable-generic-projects`, which looks like the following:
;;;###autoload
(defun ede-enable-generic-projects ()
"Enable generic project loaders."
(interactive)
(ede-generic-new-autoloader "generic-makefile" "Generic Make"
"Makefile" 'ede-generic-makefile-project)
(ede-generic-new-autoloader "generic-scons" "Generic SCons"
"SConstruct" 'ede-generic-scons-project)
(ede-generic-new-autoloader "generic-cmake" "Generic CMake"
"CMakeLists" 'ede-generic-cmake-project)
;; Super Generic found via revision control tags.
(ede-generic-new-autoloader "generic-git" "Generic Git"
".git" 'ede-generic-vc-project)
(ede-generic-new-autoloader "generic-bzr" "Generic Bazaar"
".bzr" 'ede-generic-vc-project)
(ede-generic-new-autoloader "generic-hg" "Generic Mercurial"
".hg" 'ede-generic-vc-project)
(ede-generic-new-autoloader "generic-svn" "Generic Subversions"
".svn" 'ede-generic-vc-project)
(ede-generic-new-autoloader "generic-cvs" "Generic CVS"
"CVS" 'ede-generic-vc-project)
(ede-generic-new-autoloader "generic-mtn" "Generic Monotone"
"_MTN" 'ede-generic-vc-project)
;; Take advantage of existing 'projectile' based projects.
;; @TODO - if projectile supports compile commands etc, can we
;; read that out? Howto if projectile is not part of core emacs.
(ede-generic-new-autoloader "generic-projectile" "Generic .projectile"
".projectile" 'ede-generic-vc-project)
)
These projects types (minues these autoloader ones), I assume, are defined with `ede-project`. It's docstring says:
An EDE project controls a set of TARGETS, and can also contain
multiple SUBPROJECTS.
The project defines a set of features that need to be built from
files, in addition as to controlling what to do with the file set,
such as creating distributions, compilation, and web sites.
Projects can also affect how EDE works, by changing what appears in
the EDE menu, or how some keys are bound.
But, as with the projects defined with `ede-generic-new-autoloader` they are `ede-generic-project`. If you look at ede-generic-vc-project:
(defclass ede-generic-vc-project (ede-generic-project)
()
"Generic project found via Version Control files.")
and then `ede-generic-project` itself:
(defclass ede-generic-project (ede-project-with-config
ede-project-with-config-build
ede-project-with-config-program
ede-project-with-config-c
ede-project-with-config-java)
((config-class :initform 'ede-generic-config)
(config-file-basename :initform "EDEConfig.el")
(buildfile :initform ""
:type string
:allocation :class
:documentation "The file name that identifies a project of this type.
The class allocated value is replace by different sub classes.")
)
"The baseclass for all generic EDE project types."
:abstract t)
So all of this to ask: what's the simplest way to define a new project type? I'll give a use case.
Say I have a Common Lisp project. Your project could contain 1 or more ASDF files. If your project is `my-project` then the ASDF file will be `my-project.asd`, or whatever you choose to name the project.
An easy example would be to look at [`qlot.asd`](https://github.com/fukamachi/qlot/blob/master/qlot.asd).
You could also be a [roswell](https://github.com/roswell/roswell) user, in which case your project might have a `roswell` directory. Again, you can look at the [qlot](https://github.com/fukamachi/qlot/tree/master/roswell) project for reference.
This all could be for nothing, as, at the very least, I could create functions for each mode to run shell commands (or things to send to the REPL in the case of Common Lisp) related to the project type. So say for an NPM project:
(defun project:npm-run-dev ()
"This is a bit simple, but you get the idea."
(interactive)
(project-shell-command "npm run dev"))
TL;DR - I'm curious as to how I could use Project and EDE to organize project types and create project specific functions, like with Projectile, or if this a bit more complicated and it'd be better to just create a set of functions for each project type that I work with.
EDIT: As a note of interest and another reason I or someone else might be interested in doing this `project-root` is actually generic as well. ([For those not familiar with `defgeneric` in Common Lisp.](https://gigamonkeys.com/book/object-reorientation-generic-functions.html)) As an example of it's use, here's a bit of code from `ede.el`:
(cl-defmethod project-root ((project ede-project))
(ede-project-root-directory project))
With `cl-defmethod`, `project-root` can now take an argument that is of the type (class) of `ede-project`, which is then used as an argument for `ede-project-root-directory`, which just returns the project root directory if it's a valid EDE project.
Again, defining another use of `project-root` might not be necessary. One could likely just wrap it instead, like so:
(defvar project:root-list
'("Makefile" "GNUMakefile" "CMakeLists.txt"
"pom.xml"
"Cask" "Eldev" "Keg" "Eask"
"package.json" "tsconfig.json" "jsconfig.json"
".volarrc" "volar.config.js"
"manage.py" "requirements.txt" "setup.py" "tox.ini" "Pipfile" "poetry.lock" "pyproject.toml"
"Gemfile"
"info.rkt")
"A list of named files that indicate the current directory is a project. Some people might need less or more file names.")
(defun project:project-p (dir)
"Check if the DIR if a project, containing a valid project file in ‘project:root-list’.
Returns the directory containing the valid project file, or nil if there is no matching directory."
(seq-some (lambda (f)
(locate-dominating-file dir f))
project:root-list))
(defun project:project-root (&optional dir)
"Return the current project root of DIR (defaults to ‘default-directory’).
Returns nil if not in a project."
(if (project:project-p dir)
(project-root dir)
(project-root (project-current))))
However this doesn't work with projects like the Common Lisp one I gave; where this indicating file that the directory is a project could be any named file with an `.asd` extension.