From d5f698ef8380cac3bba6f2fb731c9c1830bd6a45 Mon Sep 17 00:00:00 2001 From: Auralcat Date: Thu, 16 Apr 2020 11:35:26 -0300 Subject: [PATCH] Update project documentation We need detailed instructions for other people to use this package and contribute to the project. This should make it clearer for new devs what is going on in the code and it should give people directions about where to start looking at the codebase. There is also references to Emacs Lisp learning material. --- CONTRIBUTING.md | 75 ++++++++++++++++++++++++++- README.md | 66 ++++++++++++++++++++++-- projectile-phoenix.el | 114 +++++++++++++++++++++++++++++------------- 3 files changed, 215 insertions(+), 40 deletions(-) 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