diff --git a/CHANGELOG.md b/CHANGELOG.md index 3538ab626..154d6a907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Add new command `projectile-find-references` (bound to `C-c C-p ?` and `C-c C-p s x`). * [#1737](https://github.com/bbatsov/projectile/pull/1737): Add helpers for `dir-local-variables` for 3rd party use. Functions `projectile-add-dir-local-variable` and `projectile-delete-dir-local-variable` wrap their built-in counterparts. They always use `.dir-locals.el` from the root of the current Projectile project. * Add a new defcustom (`projectile-dirconfig-file`) controlling the name of the file used as Projectile’s root marker and configuration file. +* [#1813](https://github.com/bbatsov/projectile/pull/1813): Allow project-files to contain wildcards and allow multiple project-files per project type registration. Add a new project-type for .NET solutions. ### Changes diff --git a/doc/modules/ROOT/pages/projects.adoc b/doc/modules/ROOT/pages/projects.adoc index c39beaca6..f3db50d8f 100644 --- a/doc/modules/ROOT/pages/projects.adoc +++ b/doc/modules/ROOT/pages/projects.adoc @@ -162,8 +162,8 @@ initialization code What this does is: . add your own type of project, in this case `npm` package. -. add a list of files and/or folders in a root of the project that helps to identify the type, in this case it is only `package.json`. -. add _project-file_, which is typically the primary project configuration file. In this case that's `package.json`. +. add a list of files and/or folders in a root of the project that helps to identify the type, in this case it is only `package.json`. This can also be a function which takes a project root as argument and verifies whether that directory has the correct project structure for the type. +. add _project-file_, which is typically the primary project configuration file. In this case that's `package.json`. The value can contain wildcards and/or be a list containing multiple project files to look for. . add _compile-command_, in this case it is `npm install`. . add _test-command_, in this case it is `npm test`. . add _run-command_, in this case it is `npm start`. @@ -171,6 +171,19 @@ What this does is: Let's see a couple of more complex examples. +[source,elisp] +---- +;; .NET C# or F# projects +(projectile-register-project-type 'dotnet #'projectile-dotnet-project-p + :project-file '("?*.csproj" "?*.fsproj") + :compile "dotnet build" + :run "dotnet run" + :test "dotnet test") +---- + +This example uses _projectile-dotnet-project-p_ to validate the project's structure. +Since C# and F# project files have names containing the name of the project, it uses a list of wildcards to specify the different valid _project-file_ name patterns. + [source,elisp] ---- ;; Ruby + RSpec diff --git a/projectile.el b/projectile.el index db275a1f4..62e1d40e6 100644 --- a/projectile.el +++ b/projectile.el @@ -1181,7 +1181,7 @@ which we're looking." (null file) (string-match locate-dominating-stop-dir-regexp file))) (setq try (if (stringp name) - (projectile-file-exists-p (expand-file-name name file)) + (projectile-file-exists-p (projectile-expand-file-name-wildcard name file)) (funcall name file))) (cond (try (setq root file)) ((equal file (setq file (file-name-directory @@ -1204,7 +1204,7 @@ Return the first (topmost) matched directory or nil if not found." (projectile-locate-dominating-file dir (lambda (dir) - (cl-find-if (lambda (f) (projectile-file-exists-p (expand-file-name f dir))) + (cl-find-if (lambda (f) (projectile-file-exists-p (projectile-expand-file-name-wildcard f dir))) (or list projectile-project-root-files))))) (defun projectile-root-marked (dir) @@ -1232,9 +1232,9 @@ topmost sequence of matched directories. Nil otherwise." (projectile-locate-dominating-file dir (lambda (dir) - (and (projectile-file-exists-p (expand-file-name f dir)) + (and (projectile-file-exists-p (projectile-expand-file-name-wildcard f dir)) (or (string-match locate-dominating-stop-dir-regexp (projectile-parent dir)) - (not (projectile-file-exists-p (expand-file-name f (projectile-parent dir))))))))) + (not (projectile-file-exists-p (projectile-expand-file-name-wildcard f (projectile-parent dir))))))))) (or list projectile-project-root-files-top-down-recurring))) (defun projectile-project-root (&optional dir) @@ -2612,7 +2612,7 @@ PROJECT-ROOT is the project root." (when (or paths (null predicates)) (list :paths (cl-remove-if-not (lambda (f) - (projectile-file-exists-p (expand-file-name f project-root))) + (projectile-file-exists-p (projectile-expand-file-name-wildcard f project-root))) paths))) (when predicates (list :predicate (if (= 1 (length predicates)) @@ -2806,9 +2806,13 @@ files such as test/impl/other files as below: 'test-command test 'install-command install 'package-command package - 'run-command run))) - (when (and project-file (not (member project-file projectile-project-root-files))) - (add-to-list 'projectile-project-root-files project-file)) + 'run-command run)) + (project-files (if (listp project-file) + project-file + (list project-file)))) + (dolist (project-file project-files) + (when (and project-file (not (member project-file projectile-project-root-files))) + (add-to-list 'projectile-project-root-files project-file))) (when test-suffix (plist-put project-plist 'test-suffix test-suffix)) (when test-prefix @@ -2963,6 +2967,16 @@ it acts on the current project." (or (projectile-verify-file "Eldev" dir) (projectile-verify-file "Eldev-local" dir))) +(defun projectile-expand-file-name-wildcard (name-pattern dir) + "Expand the maybe-wildcard-containing NAME-PATTERN in DIR. +If there are results expanding a wildcard, get the first result, +otherwise expand NAME-PATTERN in DIR ignoring wildcards." + (let ((expanded (expand-file-name name-pattern dir))) + (or (if (string-match-p "[[*?]" name-pattern) + (car + (file-expand-wildcards expanded))) + expanded))) + (defun projectile-cabal-project-p (&optional dir) "Check if a project contains *.cabal files but no stack.yaml file. When DIR is specified it checks DIR's project, otherwise diff --git a/test/projectile-test.el b/test/projectile-test.el index de89580a0..a6e4e91a0 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -165,6 +165,25 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (expect (projectile-expand-root "foo/bar") :to-equal "/path/to/project/foo/bar") (expect (projectile-expand-root "./foo/bar") :to-equal "/path/to/project/foo/bar"))) +(describe "projectile-expand-file-name-wildcard" + (it "expands a filename not containing wildcards" + (expect (projectile-expand-file-name-wildcard "test" "/path/to/project/") + :to-equal "/path/to/project/test")) + (it "does not try to resolve wildcards if there are none in the pattern" + (spy-on 'file-expand-wildcards) + (expect (projectile-expand-file-name-wildcard "foo" "/path/to/project/") + :to-equal "/path/to/project/foo") + (expect (spy-calls-any 'file-expand-wildcards) :to-equal nil)) + (it "returns the first wildcard result if any exist" + (spy-on 'file-expand-wildcards + :and-return-value '("/path/to/project/one" + "/path/to/project/two")) + (expect (projectile-expand-file-name-wildcard "*" "/path/to/project") + :to-equal "/path/to/project/one")) + (it "returns the expanded result if the are no wildcard results" + (expect (projectile-expand-file-name-wildcard "*" "/path/to/project-b") + :to-equal "/path/to/project-b/*"))) + (describe "projectile--combine-plists" (it "Items in second plist override elements in first" (expect (projectile--combine-plists