diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03e429d..fff644c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,80 @@ +# Project structure +Currently, all the implementation code is in `projectile-phoenix.el`. The file is separated into the external functions (as of this moment, just the navigation functions) and the utility functions. + +The functions named like `projectile-phoenix--this` are intended as private functions. The end-user should not call them, despite Emacs showing them in the function list when calling `describe-function`, for example. The external API for the package is noted as the external functions in the file. + +To write tests, we can only check what is returned by the auxiliary functions, since most external functions deal with side effects: opening new buffers, running processes and so on. + ## Dependencies This project uses [Buttercup](https://github.com/jorgenschaefer/emacs-buttercup/) for implementing behavior-driven tests and [Cask](https://github.com/cask/cask) for dependency management. -## How to run tests +## Elisp References +If you are new to Emacs Lisp, we recommend going through the `GNU Emacs Lisp Reference Manual` (`M-x info` then search for `Emacs Lisp Intro`) and later consulting the `Emacs Lisp Reference Manual` (`M-x info` then search for `Elisp`). +This project makes heavy use of file-related functions. + +## Glossary +A `resource` is a component of a Phoenix project. +Examples: controllers, views, templates, routers, migrations. + +We define `web resources` as resources that are under the `_web` +directory: + +- Controllers +- Views +- Routers +- Channels +- Templates + +For example, if we have a project structure like this: + +``` shell +# ... +|-- lib +| |-- sample_project +| | |-- application.ex +| | `-- repo.ex +| |-- sample_project.ex +| |-- sample_project_web +| | |-- channels +| | | `-- user_socket.ex +| | |-- controllers +| | | `-- page_controller.ex +| | |-- endpoint.ex +| | |-- gettext.ex +| | |-- router.ex +| | |-- templates +| | | |-- layout +| | | | `-- app.html.eex +| | | `-- page +| | | `-- index.html.eex +| | `-- views +| | |-- error_helpers.ex +| | |-- error_view.ex +| | |-- layout_view.ex +| | `-- page_view.ex +| `-- sample_project_web.ex +|-- mix.exs +|-- mix.lock +|-- priv +| |-- gettext +| | |-- en +| | | `-- LC_MESSAGES +| | | `-- errors.po +| | `-- errors.pot +| |-- repo +| | |-- migrations +| | `-- seeds.exs +# ... +``` + +The controller would be `page_controller.ex`, the views would be `error_view.ex`, `layout_view.ex` and `page_view.ex`, the templates would be `app.html.eex` and `index.html.eex`, the channel is `user_socket.ex` and the router is `router.ex`. + +A `private resource` is defined as a resource under the `priv` directory. This includes: +- Migrations +- Translation files + +# Tests +## How to run Go to the root of the project and run `make test`. This should run Buttercup for you even if you've just cloned the project: ``` shell diff --git a/README.md b/README.md index 947c6d9..f4f55f1 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,74 @@ # Projectile-phoenix +This project is inspired by [Projectile Rails](https://github.com/asok/projectile-rails) and takes some extra code from [Projectile](https://github.com/bbatsov/projectile). +This is also an early alpha version, so expect a couple bugs. +For now the package implements navigation between some Phoenix resources. - +## Problem proposition +The reason this package came to be was: + +- I started working with Phoenix projects +- I liked the experience that I had in [Projectile Rails](https://github.com/asok/projectile-rails) +- There were no similar minor modes for working with Phoenix projects. + While Alchemist has `alchemist-phoenix-mode`, the last version covers the + previous version of Phoenix, with a different project structure. + To update `alchemist` would require more work to get around the code, and that package does a lot of stuff already. + +I wanted something that concerns itself only with Phoenix projects, and nothing else. +So we're going to delegate whatever auxiliary features we can to other projects, such as `alchemist` itself for `alchemist-iex-mode`, `mix`-related stuff to `mix.el` and so on. This project aims to offer quicker navigation to Phoenix-related files at first: - [X] Find controller - [X] Find view - [X] Find template -- Jump to test +- [X] Jump to test - Run mix tasks -The idea is to have a single navigation responsibility, offloading other features to other packages. - ## Why not use Alchemist? Alchemist offers navigation between Phoenix files with a minor mode, but it has been unmaintained for some time now. + +## Installation +This package is not in MELPA yet, so you need to clone this repository into your local machine: + +``` shell +$ git clone git@github.com:Auralcat/projectile-phoenix.git +``` + +In your Emacs configuration (usually `init.el`), include this snippet: + +``` emacs-lisp +;; Location of the cloned repository, in this example it is ~/projectile-phoenix +(add-to-list 'load-path "~/projectile-phoenix") +(require "projectile-phoenix") +``` +## Configuration +To use the package's functions through keybindings, you need to provide a prefix binding in your `init.el` like so. +This example uses "C-c .", but you're free to use any other chord you like. +``` emacs-lisp +(define-key projectile-phoenix-mode-map (kbd "C-c .") 'projectile-phoenix-command-map) +``` + +As an alternative, if you use [evil-mode](https://github.com/emacs-evil/evil) and [evil-leader](https://github.com/emacs-evil/evil), you can set a binding to `projectile-phoenix-command-map` directly like this: +``` emacs-lisp +(evil-leader/set-key-for-mode 'elixir-mode "." 'projectile-phoenix-command-map) +``` + +To activate `projectile-phoenix-mode` automatically for Phoenix projects, include this in your `init.el`: +``` emacs-lisp +(projectile-phoenix-global-mode) +``` + +## Usage +When inside a Phoenix project, press the key prefix that you configured and select a function: +| Function | Key | +| Find controller | c | +| Find migration | n | +| Find mix task | m | +| Find seed file | s | +| Find template | l | +| Find test | t | +| Find view | v | + +## Contributing +For detailed instructions about how the code is structured and project goals, please check `CONTRIBUTING.md`. +If you've found a bug or want to suggest new features, please open an issue in this repository, and thank you in advance! 💜 diff --git a/projectile-phoenix.el b/projectile-phoenix.el index 2184391..8caae4a 100644 --- a/projectile-phoenix.el +++ b/projectile-phoenix.el @@ -29,8 +29,9 @@ ;; Boston, MA 02110-1301, USA. ;;; Commentary: -;; This project is a helper thingy for Phoenix projects, much like -;; projectile-rails. But for Phoenix projects. +;; This project is a helper extension meant for Phoenix projects, much like +;; projectile-rails. +;; Currently, it implements functions to navigate between Phoenix resources in a faster way. ;;; Code: (require 'projectile) @@ -43,7 +44,7 @@ :group 'projectile) (defcustom projectile-phoenix-keymap-prefix nil - "Keymap prefix for `projectile-phoenix-mode'." + "Keymap prefix for function `projectile-phoenix-mode'." :group 'projectile-phoenix :type 'string) @@ -57,7 +58,7 @@ (define-key map (kbd "n") 'projectile-phoenix-find-migration) (define-key map (kbd "m") 'projectile-phoenix-find-mix-task) map) - "Keymap after projectile-phoenix-keymap-prefix.") + "Keymap after `projectile-phoenix-keymap-prefix'.") (defvar projectile-phoenix-mode-map (let ((map (make-sparse-keymap))) @@ -73,8 +74,8 @@ (define-minor-mode projectile-phoenix-mode "Toggle Projectile Phoenix mode. -When Projectile Phoenix mode is enabled, it adds specific keybindings to navigation functions -designed for working with Phoenix projects." +When Projectile Phoenix mode is enabled, it adds specific keybindings to +navigation functions designed for working with Phoenix projects." :initial-value nil ;; Mode line indicator :lighter " Phx" @@ -82,27 +83,41 @@ designed for working with Phoenix projects." ;;;###autoload (defun projectile-phoenix-on () - "Enable projectile-phoenix-mode for Phoenix projects automatically." - (if (projectile-phoenix-project-p) + "Enable variable `projectile-phoenix-mode' inside a Phoenix project. + +This function is meant to be used with `define-globalized-minor-mode' in order +to enable `projectile-phoenix-mode' automatically, without needing to add a hook +to Elixir mode or activating the mode manually." + (when (projectile-phoenix-project-p) (projectile-phoenix-mode +1) )) +(defun projectile-phoenix-off () + "Disable variable `projectile-phoenix-mode'." + (projectile-phoenix-mode -1) + ) + +;;;###autoload +(define-globalized-minor-mode projectile-phoenix-global-mode + projectile-phoenix-mode + projectile-phoenix-on) + ;;; External functions ;; TODO: Improve the coverage of resource regexps. (defun projectile-phoenix-find-controller () - "Search for a controller inside the controllers directory and open it in a buffer." + "Search for a controller inside the project and open it in a buffer." (interactive) (projectile-phoenix--find-web-resource "controller" "_controller.ex$") ) (defun projectile-phoenix-find-view () - "Search for a view inside the views directory and open it in a buffer." + "Search for a view inside the project and open it in a buffer." (interactive) (projectile-phoenix--find-web-resource "view" "_view.ex$") ) (defun projectile-phoenix-find-template () - "Search for a template inside the templates directory and open it in a buffer." + "Search for a template inside the project and open it in a buffer." (interactive) (projectile-phoenix--find-web-resource "template" ".html.eex$")) @@ -113,13 +128,16 @@ designed for working with Phoenix projects." ) (defun projectile-phoenix-find-mix-task () - "Search for a mix task in the project and open it in a new buffer." + "Search for a mix task in the project and open it in a new buffer." (interactive) (projectile-phoenix--find-mix-task) ) (defun projectile-phoenix-find-test () - "This is a wrapper function for projectile-find-test-file." + "This is a wrapper function for `projectile-find-test-file'. + +Opens the test directory in the project and shows a list of candidates +to the user. When the user chooses one, open it in a new buffer." (interactive) (projectile-find-test-file) ) @@ -137,7 +155,7 @@ if they wish so." ) (if (file-exists-p seeds-file-location) (find-file seeds-file-location) - (if (y-or-n-p "The seeds.exs file could not be found in this project. Create one?") + (if (y-or-n-p "The seeds.exs file could not be found in this project. Create one? ") (find-file seeds-file-location)) ) ) @@ -145,7 +163,9 @@ if they wish so." ;;; Utilities (defun projectile-phoenix--find-web-resource (resource resource-regexp) - "Show a list of candidates for the required web RESOURCE matching RESOURCE-REGEXP to the user and open the chosen candidate in a new buffer. + "Show a list of candidates for the required web RESOURCE. +It should match the provided RESOURCE-REGEXP and open the chosen candidate +in a new buffer. Web resources include: @@ -167,7 +187,7 @@ Web resources include: (message "Please call this function inside a Phoenix project.")))) (defun projectile-phoenix--find-migration () - "Show a list of candidates for migrations to the user and open the chosen candidate in a new buffer." + "Show a list of migrations to the user and open the candidate in a new buffer." (let* ( (prompt "Migration: ") (choices-hash (projectile-phoenix-hash-migration-choices)) @@ -182,7 +202,7 @@ Web resources include: (message "Please call this function inside a Phoenix project.")))) (defun projectile-phoenix--find-mix-task () - "Show a list of candidates for mix tasks to the user and open the chosen candidate in a new buffer." + "Show a list of mix tasks to the user and open the candidate in a new buffer." (let* ( (prompt "Task: ") (choices-hash (projectile-phoenix-hash-mix-task-choices)) @@ -208,25 +228,34 @@ Web resources include: (expand-file-name "lib" (projectile-project-root))) ) -(defun projectile-phoenix-web-resource-candidates (web-resource web-resource-regexp) - "Return a list of base WEB-RESOURCE candidates for selection that match the provided WEB-RESOURCE-REGEXP." +(defun projectile-phoenix-web-resource-candidates (resource resource-regexp) + "Return a list of base RESOURCE candidates for selection. + +These candidates should match the provided RESOURCE-REGEXP." (let ((web-resource-choices - (directory-files-recursively (projectile-phoenix-web-resources-directory web-resource) web-resource-regexp))) + (directory-files-recursively (projectile-phoenix-web-resources-directory resource) resource-regexp))) (mapcar (lambda (c) (file-name-nondirectory c)) web-resource-choices) ) ) (defun projectile-phoenix-hash-web-resource-choices (resource resource-regexp) - "Generate a key-pair relationship between the base file name (without the extension) and the file's absolute path. + "Create a hash table linking the base file name and the file's absolute path. + +The table is generated based on the provided web RESOURCE which matches the +provided web RESOURCE-REGEXP. It generates a relation like this for a controller, for instance: -- sample -> /lib/_web/controllers/sample_controller.ex -- cogs -> /lib/_web/controllers/cogs_controller.ex -- nested/sprockets -> /lib/_web/controllers/nested/sprockets_controller.ex +- sample: + /lib/_web/controllers/sample_controller.ex +- cogs: + /lib/_web/controllers/cogs_controller.ex +- nested/sprockets: + /lib/_web/controllers/nested/sprockets_controller.ex -This function focuses on the processing of web resources, like controllers, views and templates. -For resources like tests and migrations, please check projectile-phoenix-hash-resource-choices." +This function focuses on the processing of web resources, like controllers, +views and templates. For resources like tests and migrations, please check +projectile-phoenix-hash-resource-choices." (let* ( (base-hash (make-hash-table :test 'equal)) (file-collection (directory-files-recursively (projectile-phoenix-web-resources-directory resource) resource-regexp)) @@ -237,7 +266,7 @@ For resources like tests and migrations, please check projectile-phoenix-hash-re )) (defun projectile-phoenix-hash-migration-choices () - "Generate a key-pair relationship between the base migration name and the migration's absolute path." + "Create a hash-table linking the base migration name and the migration's absolute path." (let* ( (base-hash (make-hash-table :test 'equal)) (migrations-dir (expand-file-name "priv/repo/migrations" (projectile-project-root))) @@ -250,7 +279,7 @@ For resources like tests and migrations, please check projectile-phoenix-hash-re )) (defun projectile-phoenix-hash-mix-task-choices () - "Generate a key-pair relationship between the base task name and the task's absolute path." + "Create a hash-table linking the base task name and the task's absolute path." (let* ( (base-hash (make-hash-table :test 'equal)) (mix-tasks-dir (expand-file-name "lib/mix/tasks" (projectile-project-root))) @@ -269,7 +298,17 @@ For resources like tests and migrations, please check projectile-phoenix-hash-re ;; TODO: Add examples to the documentation of the functions in this file. ;; Maybe we could also give this a better name? (defun projectile-phoenix-context-resource-name (resource-path resource resource-trim-regexp) - "Return the context-aware name of the resource given its RESOURCE-BASE-DIR and the RESOURCE-REGEXP it should match." + "Return a name for the RESOURCE based on its context in the project. + +It takes the RESOURCE-PATH to trim the absolute path of the files and an +additional RESOURCE-TRIM-REGEXP to reduce the path to a context-aware name +for the type of RESOURCE in the project. + +Example: + +A controller in +/lib/_web/controllers/nested/foo_controller.ex +will be named 'nested/foo'." (let* ((resource-base-dir (projectile-phoenix-web-resources-directory resource))) (replace-regexp-in-string resource-trim-regexp "" @@ -282,16 +321,21 @@ For resources like tests and migrations, please check projectile-phoenix-hash-re (defun projectile-phoenix-project-p () "Return t if called inside a Phoenix project. - It will return nil otherwise. -A Phoenix project is defined by: -- A _web directory -- A _web.ex file. -- A mix.exs file, since it's a class of Mix project." +One of the details of Phoenix projects is that they have a +_web.ex file under /lib/. + +So if that file is present in the project and there is also a +_web directory as well, chances that that we're working +with a Phoenix project." (let* ( (phoenix-web-file (concat (projectile-project-name) "_web.ex"))) - (file-exists-p (concat (projectile-project-root) "/" "lib/" phoenix-web-file)))) + (and + (file-exists-p (concat (projectile-project-root) "/" "lib/" phoenix-web-file)) + (file-exists-p (projectile-phoenix--web-directory)) + ) + )) (provide 'projectile-phoenix) ;;; projectile-phoenix.el ends here