Skip to content

Commit

Permalink
Merge pull request #104 from membraneframework/MS-271-create-a-docume…
Browse files Browse the repository at this point in the history
…ntation-generator-for-the-native-code

MS-271 Create a documentation generator for the native code
  • Loading branch information
Janix4000 authored Dec 5, 2022
2 parents 47dc08d + a733ff8 commit 81c9cae
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 37 deletions.
25 changes: 22 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ workflows:
version: 2
build:
jobs:
- elixir/build_test
- elixir/test
- elixir/lint
- elixir/build_test:
filters: &filters
tags:
only: /v.*/
- elixir/test:
filters:
<<: *filters
- elixir/lint:
filters:
<<: *filters
- elixir/hex_publish:
requires:
- elixir/build_test
- elixir/test
- elixir/lint
context:
- Deployment
filters:
branches:
ignore: /.*/
tags:
only: /v.*/
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule MyApp.Mixfile do

defp deps() do
[
{:bundlex, "~> 1.0.1"}
{:bundlex, "~> 1.1.0"}
]
end
end
Expand Down Expand Up @@ -193,6 +193,31 @@ As in the case of NIFs, CNodes compiled with Bundlex can be used like any other
Similarly to CNodes Bundlex provides `Bundlex.Port` module for a little easier interacting with Ports.
Please refer to the module's documentation to see how to use it.
### Documentation of the native code
Bundlex provides a way to generate documentation of the native code. The documentation is generated using [Doxygen](http://www.doxygen.nl/).
To do so, run `$ mix bundlex.doxygen` command. The documentation is generated for each native separately. The documentation of the native `project_name` will be generated in `doc/bundlex/project_name` directory. Additionally, hex doc page with the link to the Doxygen documentation is generated in the `pages/doxygen/project_name.md` and should be included in the `mix.exs` file:
```elixir
defp docs do
[
extras: [
"pages/doxygen/project_name.md",
...
],
...
]
end
```
If you want to keep own changes in the `pages/doxygen/project_name.md` file, you can use `--no` option to skip the generation of this file. Otherwise, if you want the file to be always overwritten, use `--yes` option.

After that, the documentation can be generated with `mix docs` command.

### Include native documentation in the hex docs

To include the native documentation in the hex docs, you need to generate the documentation with `$ mix bundlex.doxygen` command and include hex page in the `extras`, before running `$ mix hex.publish` command.

## More examples

More advanced examples can be found in our [test_projects](https://github.com/membraneframework/bundlex/tree/master/test_projects)
Expand Down
168 changes: 168 additions & 0 deletions lib/bundlex/doxygen.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
defmodule Bundlex.Doxygen do
@moduledoc """
Module responsible for generating doxygen documentation for Bundlex projects.
"""

alias Bundlex.Project

@type doxygen_t :: %{
project_name: String.t(),
doxyfile_path: String.t(),
doxygen_path: String.t(),
page_path: String.t()
}

@doc """
Prepares struct with all necessary filepaths for the native documentation
"""
@spec doxygen(Project.t()) :: doxygen_t()
def doxygen(project) do
project_name = Atom.to_string(project.app)

%{
project_name: project_name,
doxyfile_path: doxyfile_path(project),
doxygen_path: doxygen_path(project),
page_path: page_path(project)
}
end

defp doxyfile_path(project) do
project_name = "#{project.app}"
project_dirpath = Path.join(["doc", "doxygen", project_name])
Path.join(project_dirpath, "Doxyfile")
end

defp doxygen_path(project) do
project_name = "#{project.app}"
Path.join(["doc", "doxygen", project_name])
end

defp page_path(project) do
Path.join(["pages", "doxygen", "#{project.app}.md"])
end

@doc """
Generates doxyfile in the c_src/project directory for Bundlex project.
"""
@spec generate_doxyfile(doxygen_t()) :: :ok
def generate_doxyfile(doxygen) do
create_doxyfile_template(doxygen)

keywords_to_change = keywords_to_change(doxygen)
update_doxyfile_keywords(doxygen, keywords_to_change)
end

defp create_doxyfile_template(doxygen) do
ensure_doxygen_dir_existence(doxygen)
cmd_bundlex(["-g", doxygen.doxyfile_path])
end

defp cmd_bundlex(args) do
with {_res, 0} <-
System.cmd("doxygen", args) do
:ok
else
{output, status} ->
error_message =
"Running doxygen command with args: #{inspect(args)} failed with exit code: #{inspect(status)} and output: #{inspect(output)}"

raise error_message
end
rescue
e in ErlangError ->
case e do
%ErlangError{original: :enoent} ->
reraise Bundlex.Doxygen.Error,
[
message:
"Failed to run `doxygen` command with args #{inspect(args)}. Ensure, that you have `doxygen` available on your machine"
],
__STACKTRACE__

e ->
reraise e, __STACKTRACE__
end
end

defp keywords_to_change(doxygen) do
%{
"PROJECT_NAME" => doxygen.project_name,
"OUTPUT_DIRECTORY" => doxygen.doxygen_path,
"TAB_SIZE" => "2",
"BUILTIN_STL_SUPPORT" => "YES",
"RECURSIVE" => "YES",
"GENERATE_LATEX" => "NO",
"INPUT" => Path.join(["c_src", doxygen.project_name]),
"EXTRACT_STATIC" => "YES"
}
end

defp update_doxyfile_keywords(doxygen, keywords_to_change) do
doxyfile_path = doxygen.doxyfile_path

File.stream!(doxyfile_path)
|> Stream.map(fn line ->
if comment?(line) do
line
else
replace_keywords(line, keywords_to_change)
end
end)
|> Enum.join()
|> then(&File.write!(doxyfile_path, &1))
end

defp comment?(line) do
String.starts_with?(line, "#")
end

defp replace_keywords(line, keywords_to_change) do
Enum.reduce(keywords_to_change, line, fn {keyword, value}, acc ->
String.replace(acc, ~r/(#{keyword}\s*=)\s*(.*)\n/, "\\1 \"#{value}\"\n")
end)
end

@doc """
Generates html doxygen documentation for the Bundlex project. Doxyfile must be generated before.
"""
@spec generate_doxygen_documentation(doxygen_t()) :: :ok
def generate_doxygen_documentation(doxygen) do
ensure_doxygen_dir_existence(doxygen)

cmd_bundlex([doxygen.doxyfile_path])
end

defp ensure_doxygen_dir_existence(doxygen) do
if not File.exists?(doxygen.doxygen_path) do
File.mkdir_p!(doxygen.doxygen_path)
end

File.touch!(Path.join(["doc", ".build"]))
end

@doc """
Generates page for the Bundlex project in the pages/doxygen directory.
Page must be manually added to the docs extras in the mix.exs.
Page contains only link to the doxygen html documentation.
"""
@spec generate_hex_page(doxygen_t()) :: :ok
def generate_hex_page(doxygen) do
pages_dirpath = Path.dirname(doxygen.page_path)

if not File.exists?(pages_dirpath) do
File.mkdir_p!(pages_dirpath)
end

[_doc | doxygen_path] = Path.split(doxygen.doxygen_path)

html_filepath = Path.join(["."] ++ doxygen_path ++ ["html", "index.html"])

page = """
# Native code documentation
[Doxygen documentation of the native code](#{html_filepath})
"""

File.write!(doxygen.page_path, page)
end
end
3 changes: 3 additions & 0 deletions lib/bundlex/doxygen/error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Bundlex.Doxygen.Error do
defexception [:message]
end
90 changes: 90 additions & 0 deletions lib/mix/tasks/bundlex.doxygen.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
defmodule Mix.Tasks.Bundlex.Doxygen do
@shortdoc "Generates doxygen documentation for Bundlex project"
@moduledoc """
#{@shortdoc}
Accepts the following command line arguments:
- `--yes`, `-y` - skips confirmation prompt and overwrites existing meta files
- `--no`, `-n` - skips confirmation prompt and does not overwrite existing meta files
"""

use Mix.Task

alias Bundlex.{Doxygen, Output, Project}
alias Bundlex.Helper.MixHelper

@impl Mix.Task
def run(args) do
{:ok, _apps} = Application.ensure_all_started(:bundlex)

skip_overwrite_check? = "-y" in args or "--yes" in args
always_overwrite? = "-n" in args or "--no" in args

if skip_overwrite_check? and always_overwrite? do
Mix.raise("Cannot use both --yes and --no options")
end

app = MixHelper.get_app!()

project = get_project(app)

doxygen = Doxygen.doxygen(project)

Doxygen.generate_doxyfile(doxygen)

Doxygen.generate_doxygen_documentation(doxygen)

if skip_overwrite_check? do
Doxygen.generate_hex_page(doxygen)
else
overwrite? =
always_overwrite? or Mix.shell().yes?("Do you want to overwrite existing hex page?")

if overwrite? do
Doxygen.generate_hex_page(doxygen)
else
Output.info("Skipping hex page generation")
end
end

unless page_included?(doxygen.page_path) do
example_docs = """
defp docs do
[
extras: [
"#{doxygen.page_path}",
...
],
...
]
end
"""

Output.info("""
Doxygen documentation page is not included in the project docs.
Add the following snippet to your mix.exs file:
#{example_docs}
""")
end
end

defp get_project(app) do
with {:ok, project} <- Project.get(app) do
project
else
{:error, reason} ->
Output.raise("Cannot get project for app: #{inspect(app)}, reason: #{inspect(reason)}")
end
end

defp page_included?(doxygen_page) do
config = Mix.Project.config()

with {:ok, docs} <- Keyword.fetch(config, :docs),
{:ok, extras} <- Keyword.fetch(docs, :extras) do
doxygen_page in extras
else
:error -> false
end
end
end
File renamed without changes.
3 changes: 1 addition & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
defmodule Bundlex.Mixfile do
use Mix.Project

@version "1.0.1"

@version "1.1.0"
@github_url "https://github.com/membraneframework/bundlex"

def project do
Expand Down
14 changes: 7 additions & 7 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
%{
"bunch": {:hex, :bunch, "1.3.1", "f8fe80042f9eb474ef2801ae2c9372f9b13d11e7053265dcfc24b9d912e3750b", [:mix], [], "hexpm", "00e21b16ff9bb698b728a01a2fc4b3bf7fc0e87c4bb9c6e4a442324aa8c5e567"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"},
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"bunch": {:hex, :bunch, "1.4.1", "96176220b2024f715e9e85051f18ee98bbb42be77728a0165cdc3c01c87e483b", [:mix], [], "hexpm", "ff0bb584267f32a1541e7299fefe22a185e269b4081983bd6c5b0d56369f9037"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
"ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
Expand Down
Loading

0 comments on commit 81c9cae

Please sign in to comment.