Apr 21, 2022

Code hygiene with Elixir - Part 3

In our last post we further refined our Boundary setup. Today we’ll be concentrating on ExDoc.

If you followed along with part 2 you can continue with the code from there. Otherwise you can grab today’s starting off point by:

Terminal
mkdir <some new directory>
cd <the new directory>
Terminal
git init
git remote add origin git@github.com:riebeekn/elixir_code_hygiene.git
git fetch origin e29178674c360d9017f49001d5c8a9354ffc5055
git reset --hard FETCH_HEAD

You should now see the following git history.

Terminal
git log --pretty=oneline

And now you can install the existing dependencies, and set up the database.

Terminal
mix deps.get
Terminal
mix do ecto.drop, ecto.create, ecto.migrate

Now let’s take a closer look at ExDoc.

What we currently have

We currently have a default set-up of ExDoc in our project, we can run the docs via a mix task:

Terminal
mix docs

And then view the docs.

Terminal
open doc/index.html

If we click the modules tab we’ll see docs for the various modules in our project.

Not bad! I think we could improve things with a bit of organization however.

Organizing the docs

We can provide some organization by updating the mix.exs file.

We’ll make a number of changes to the mix file, let’s go over them one by one.

First off, let’s update the main project section.

/mix.exs
defmodule CodeHygiene.MixProject do
  use Mix.Project

  # these are new
  @version "0.1.0"
  @source_url "https://github.com/riebeekn/elixir_code_hygiene"

  def project do
    [
      app: :code_hygiene,
      # replaced hard-coded version with the @version attribute
      version: @version,
      elixir: "~> 1.12",
      elixirc_paths: elixirc_paths(Mix.env()),
      compilers: [:boundary, :gettext] ++ Mix.compilers(),
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps(),
      test_coverage: [tool: ExCoveralls],
      preferred_cli_env: [
        coveralls: :test,
        "coveralls.detail": :test,
        "coveralls.post": :test,
        "coveralls.html": :test
      ],
      # these are also new
      name: "Code Hygiene example app",
      docs: docs()
    ]
  end
  ...

We’ve added some attributes for the app version and source code location of the repo. We then add a name and docs attribute to the project function. The docs attribute calls out to a new function we’ll write which will handle the actual doc organization:

/mix.exs
defp docs do
  [
    main: "CodeHygiene",
    assets: "docs/assets",
    logo: "docs/assets/images/logo.svg",
    source_ref: "v#{@version}",
    source_url: @source_url,
    formatters: ["html"],
    groups_for_modules: groups_for_modules()
  ]
end

Everything is pretty self explanatory. We set an assets directory for any assets such as images we want to add to our docs; we set a logo for the docs; we set the default page for the docs (via the main attribute); we set the version and source url of the repo the docs refer to; we specifiy that we only want to generate html docs; and finally call out to a new function groups_for_modules which will do the grouping of the docs.

We need to create the directories we’ve specified above:

Terminal
mkdir -p docs/assets/images

And we can throw this image into the docs/assets/images folder, renaming it to logo.svg

Now lets create the groups_for_modules function.

/mix.exs
defp groups_for_modules do
  [
    API: [
      CodeHygiene,
      CodeHygiene.Products,
      CodeHygiene.Repo
    ],
    Accounts: [
      CodeHygiene.Accounts,
      CodeHygiene.Accounts.UserNotifier,
      CodeHygiene.Accounts.UserToken,
      CodeHygiene.Accounts.User
    ],
    "Accounts - Frontend": [
      CodeHygieneWeb.UserAuth,
      CodeHygieneWeb.UserConfirmationController,
      CodeHygieneWeb.UserRegistrationController,
      CodeHygieneWeb.UserResetPasswordController,
      CodeHygieneWeb.UserSessionController,
      CodeHygieneWeb.UserSettingsController
    ],
    Phoenix: [
      CodeHygieneWeb,
      CodeHygieneWeb.Endpoint,
      CodeHygieneWeb.ErrorHelpers,
      CodeHygieneWeb.Gettext,
      CodeHygieneWeb.LiveHelpers,
      CodeHygieneWeb.Router,
      CodeHygieneWeb.Router.Helpers
    ],
    Schemas: [
      CodeHygieneSchema,
      CodeHygieneSchema.Product
    ]
  ]
end

Simple, we’re just providing some grouping for how our modules will display in the docs.

There are also a number of modules in our project that don’t currently have moduledocs and which we want to exclude from the generated docs. We accomplish this by adding a @moduledoc false attribute to these modules, for example:

/lib/code_hygiene_web/controllers/page_controller.ex
defmodule CodeHygieneWeb.PageController do
  @moduledoc false

We need to add this attribute to the following files:

  • lib/code_hygiene_web/controllers/page_controller.ex
  • lib/code_hygiene_web/views/error_view.ex
  • lib/code_hygiene_web/views/layout_view.ex
  • lib/code_hygiene_web/views/page_view.ex
  • lib/code_hygiene_web/views/user_confirmation_view.ex
  • lib/code_hygiene_web/views/user_registration_view.ex
  • lib/code_hygiene_web/views/user_reset_password_view.ex
  • lib/code_hygiene_web/views/user_session_view.ex
  • lib/code_hygiene_web/views/user_settings_view.ex

Now we’ll see the docs are a little more organized:

Terminal
mix docs
open doc/index.html

Nice! We have a logo, name and version included in our docs and the modules are grouped in a logical manner.

Our final bit of exploration into the configuration of ExDoc is to see how we can go about documenting things that aren’t modules.

Using ExDoc to document more than just modules

Say you have a developer guide for your project or some other sort of documentation that isn’t specific to a module but which you’d like to include in your docs. ExDoc has you covered. First off let’s add a changelog to the project. We’ll just populate it with some fake entries.

Adding a CHANGELOG

Terminal
touch CHANGELOG.md
/CHANGELOG.md
# Change Log
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [1.0.3] - 2022-01-15
### Changed
- Changed stuff!
- Changed more stuff!

## [1.0.2] - 2021-12-15
### Fixed
- Fixed that terrible bug!

### Changed
- Updated the way something works.

### Added
- Added some new stuff!

## [1.0.1] - 2021-11-30
### Added
...

To reference this file in our docs, we need a new section in the docs function, we’ll add a line to call out to an extras function.

/mix.exs
defp docs do
  [
    main: "CodeHygiene",
    assets: "docs/assets",
    logo: "docs/assets/images/logo.svg",
    source_ref: "v#{@version}",
    source_url: @source_url,
    formatters: ["html"],
    groups_for_modules: groups_for_modules(),
    extras: extras()
  ]
end

We then need to create the extras function.

/mix.exs
defp extras do
  [
    "CHANGELOG.md"
  ]
end

And that’s it! You can use the same technique for adding any sort of file to the generated docs.

If we generate our docs we’ll see our CHANGELOG show up under the Pages tab.

The last thing we’ll look at is adding guides to our docs.

Adding guides

We can add guides to our docs via the extras function. We can further provide grouping for the guides via a groups_for_extras function.

/mix.exs
defp docs do
  [
    main: "CodeHygiene",
    assets: "docs/assets",
    logo: "docs/assets/images/logo.svg",
    source_ref: "v#{@version}",
    source_url: @source_url,
    formatters: ["html"],
    groups_for_modules: groups_for_modules(),
    extras: extras(),
    groups_for_extras: groups_for_extras(),
  ]
end

defp extras do
  [
    "CHANGELOG.md",

    # Guides
    "docs/guides/dev_setup.md"
  ]
end

defp groups_for_extras do
  [
    Guides: ~r{guides/[^\/]+\.md}
  ]
end

Let’s now create our “guide”. All we are going to do is create a fake dev setup guide with some Lorem text. We’ll also throw the Phoenix logo in there as well just to make sure assets link correctly from the docs.

So first, off we’ll create the markdown file for the guide.

Terminal
mkdir docs/guides
touch docs/guides/dev_setup.md

Now we’ll copy the Phoenix logo into the assets folder we created earlier.

Terminal
mkdir -p docs/assets/images/guides/dev_setup
cp priv/static/images/phoenix.png docs/assets/images/guides/dev_setup

And finally fill in some fake content.

/guides/dev_setup.md
# Dev Setup
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

<img src="assets/images/guides/dev_setup/phoenix.png" />

Let’s see what we got!

Terminal
mix docs
open doc/index.html

Looking good! That does it for our ExDoc organization… now would be a good time to commit our changes, and then before bailing for the day let’s look at how we can host our docs online via GitHub.

Hosting the Docs on GitHub

Generating the documentation locally is fine, but it would be great if it was available online… and luckily this is very simple, we just need a new GitHub action.

We’ll create a new YAML file for the action:

Terminal
touch .github/workflows/docs.yml
/.github/workflows/docs.yml
name: Publish docs
on:
  push:
    branches:
      - main

jobs:
  generateDocs:
    name: Generate project documentation
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Elixir
        uses: erlef/setup-beam@v1
        with:
          elixir-version: "1.13.3"
          otp-version: "24.2"
      - name: Build docs
        uses: lee-dohm/generate-elixir-docs@v1
      - name: Publish to Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./doc

This action will create a new branch called gh-pages which will contain the docs. On any push to main the docs will be regenerated and available online, pretty freaking neat!

Let’s try it out… if we push our code to GitHub we’ll see the new Publish docs action we created.

And if we take a look at the branches in our repo, we now have a new gh_pages branch.

To host the docs we need to go to the Settings –> Pages section of the GitHub repo and set the “source” for the GitHub Pages, we just set the branch to gh_pages and keep the folder as root.

After clicking save, we see a new GitHub action pages-build-deployment is triggered.

After the action completes our docs are live!

Summary

That’s it for this series of posts on code hygiene tools. Getting these tools set up can take a bit of effort but IMO is well worth it!

Today’s code

If you want to retrieve the GitHub commit that corresponds to today’s code:

Terminal
mkdir <some new directory>
cd <the new directory>
Terminal
git init
git remote add origin git@github.com:riebeekn/elixir_code_hygiene.git
git fetch origin 600642de0ed22dc49c5d2ce66accb461960bce96
git reset --hard FETCH_HEAD

You should now see the expected git history.

Terminal
git log --pretty=oneline

Thanks for reading and I hope you enjoyed the post!

References

A couple of great resources I found helpful regarding how to get the most out of ExDoc are:



Comment on this post!