-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #104 from membraneframework/MS-271-create-a-docume…
…ntation-generator-for-the-native-code MS-271 Create a documentation generator for the native code
- Loading branch information
Showing
12 changed files
with
367 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
defmodule Bundlex.Doxygen.Error do | ||
defexception [:message] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.