Skip to content

Commit

Permalink
Allow multiple project-files per type and wildcards in project-files
Browse files Browse the repository at this point in the history
  • Loading branch information
toshokan authored and bbatsov committed Nov 18, 2022
1 parent 7d414ea commit 1654956
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 15 additions & 2 deletions doc/modules/ROOT/pages/projects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,28 @@ 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`.
. add test files suffix for toggling between implementation/test files, in this case it is `.spec`, so the implementation/test file pair could be `service.js`/`service.spec.js` for example.

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
Expand Down
30 changes: 22 additions & 8 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions test/projectile-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 1654956

Please sign in to comment.