zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

docs.ex (17756B)


      1 defmodule Mix.Tasks.Docs do
      2   use Mix.Task
      3 
      4   @shortdoc "Generate documentation for the project"
      5   @requirements ["compile"]
      6 
      7   @moduledoc ~S"""
      8   Uses ExDoc to generate a static web page from the project documentation.
      9 
     10   ## Command line options
     11 
     12     * `--canonical`, `-n` - Indicate the preferred URL with
     13       rel="canonical" link element, defaults to no canonical path
     14 
     15     * `--formatter`, `-f` - Which formatters to use, "html" or
     16       "epub". This option can be given more than once. By default,
     17       both html and epub are generated.
     18 
     19     * `--language` - Specifies the language to annotate the
     20       EPUB output in valid [BCP 47](https://tools.ietf.org/html/bcp47)
     21 
     22     * `--open` - open browser window pointed to the documentation
     23 
     24     * `--output`, `-o` - Output directory for the generated
     25       docs, default: `"doc"`
     26 
     27     * `--proglang` - Chooses the main programming language: "elixir"
     28       or "erlang"
     29 
     30   The command line options have higher precedence than the options
     31   specified in your `mix.exs` file below.
     32 
     33   ## Configuration
     34 
     35   ExDoc will automatically pull in information from your project,
     36   like the application and version. However, you may want to set
     37   `:name`, `:source_url` and `:homepage_url` to have a nicer output
     38   from ExDoc, for example:
     39 
     40       def project do
     41         [
     42           app: :my_app,
     43           version: "0.1.0-dev",
     44           deps: deps(),
     45 
     46           # Docs
     47           name: "My App",
     48           source_url: "https://github.com/USER/PROJECT",
     49           homepage_url: "http://YOUR_PROJECT_HOMEPAGE",
     50           docs: [
     51             main: "MyApp", # The main page in the docs
     52             logo: "path/to/logo.png",
     53             extras: ["README.md"]
     54           ]
     55         ]
     56       end
     57 
     58   ExDoc also allows configuration specific to the documentation to
     59   be set. The following options should be put under the `:docs` key
     60   in your project's main configuration. The `:docs` options should
     61   be a keyword list or a function returning a keyword list that will
     62   be lazily executed.
     63 
     64     * `:api_reference` - Whether to generate `api-reference.html`; default: `true`.
     65       If this is set to false, `:main` must also be set.
     66 
     67     * `:assets` - Path to a directory that will be copied as is to the "assets"
     68       directory in the output path. Its entries may be referenced in your docs
     69       under "assets/ASSET.EXTENSION"; defaults to no assets directory.
     70 
     71     * `:authors` - List of authors for the generated docs or epub.
     72 
     73     * `:before_closing_body_tag` - a function that takes as argument an atom specifying
     74       the formatter being used (`:html` or `:epub`) and returns a literal HTML string
     75       to be included just before the closing body tag (`</body>`).
     76       The atom given as argument can be used to include different content in both formats.
     77       Useful to inject custom assets, such as Javascript.
     78 
     79     * `:before_closing_head_tag` - a function that takes as argument an atom specifying
     80       the formatter being used (`:html` or `:epub`) and returns a literal HTML string
     81       to be included just before the closing head tag (`</head>`).
     82       The atom given as argument can be used to include different content in both formats.
     83       Useful to inject custom assets, such as CSS stylesheets.
     84 
     85     * `:canonical` - String that defines the preferred URL with the rel="canonical"
     86       element; defaults to no canonical path.
     87 
     88     * `:cover` - Path to the epub cover image (only PNG or JPEG accepted)
     89       The image size should be around 1600x2400. When specified, the cover will be placed under
     90       the "assets" directory in the output path under the name "cover" and the
     91       appropriate extension. This option has no effect when using the "html" formatter.
     92 
     93     * `:deps` - A keyword list application names and their documentation URL.
     94       ExDoc will by default include all dependencies and assume they are hosted on
     95       HexDocs. This can be overridden by your own values. Example: `[plug: "https://myserver/plug/"]`
     96 
     97     * `:extra_section` - String that defines the section title of the additional
     98       Markdown and plain text pages; default: "PAGES". Example: "GUIDES"
     99 
    100     * `:extras` - List of paths to additional Markdown (`.md` extension), Live Markdown
    101       (`.livemd` extension), and plain text pages to add to the documentation. You can
    102       also specify keyword pairs to customize the generated filename and title of each
    103       extra page; default: `[]`. Example:
    104       `["README.md", "LICENSE", "CONTRIBUTING.md": [filename: "contributing", title: "Contributing"]]`
    105 
    106     * `:filter_modules` - Include only modules that match the given value. The
    107       value can be a regex, a string (representing a regex), or a two-arity
    108       function that receives the module and its metadata and returns true if the
    109       module must be included. If a string or a regex is given, it will be matched
    110       against the complete module name (which includes the "Elixir." prefix for
    111       Elixir modules). If a module has `@moduledoc false`, then it is always excluded.
    112 
    113     * `:formatters` - Formatter to use; default: ["html", "epub"], options: "html", "epub".
    114 
    115     * `:groups_for_extras`, `:groups_for_modules`, `:groups_for_functions` - See the "Groups" section
    116 
    117     * `:ignore_apps` - Apps to be ignored when generating documentation in an umbrella project.
    118       Receives a list of atoms. Example: `[:first_app, :second_app]`.
    119 
    120     * `:javascript_config_path` - Path of an additional JavaScript file to be included on all pages
    121       to provide up-to-date data for features like the version dropdown - See the "Additional
    122       JavaScript config" section. Example: `"../versions.js"`
    123 
    124     * `:language` - Identify the primary language of the documents, its value must be
    125       a valid [BCP 47](https://tools.ietf.org/html/bcp47) language tag; default: "en"
    126 
    127     * `:logo` - Path to the image logo of the project (only PNG or JPEG accepted)
    128       The image size will be 64x64. When specified, the logo will be placed under
    129       the "assets" directory in the output path under the name "logo" and the
    130       appropriate extension.
    131 
    132     * `:main` - Main page of the documentation. It may be a module or a
    133       generated page, like "Plug" or "api-reference"; default: "api-reference".
    134 
    135     * `:markdown_processor` - The markdown processor to use,
    136       either `module()` or `{module(), keyword()}` to provide configuration options;
    137 
    138     * `:nest_modules_by_prefix` - See the "Nesting" section
    139 
    140     * `:output` - Output directory for the generated docs; default: "doc".
    141       May be overridden by command line argument.
    142 
    143     * `:skip_undefined_reference_warnings_on` - ExDoc warns when it can't create a `Mod.fun/arity`
    144       reference in the current project docs e.g. because of a typo. This list controls where to
    145       skip the warnings, for a given module/function/callback/type (e.g.: `["Foo", "Bar.baz/0"]`)
    146       or on a given file (e.g.: `["pages/deprecations.md"]`); default: `[]`.
    147 
    148     * `:source_beam` - Path to the beam directory; default: mix's compile path.
    149 
    150     * `:source_ref` - The branch/commit/tag used for source link inference;
    151       default: "main".
    152 
    153     * `:source_url_pattern` - Public URL of the project for source links. This is derived
    154       automatically from the project's `:source_url` and `:source_ref` when using one of
    155       the supported public hosting services (currently GitHub, GitLab, or Bitbucket). If
    156       you are using one of those services with their default public hostname, you do not
    157       need to set this configuration.
    158 
    159       However, if using a different solution, or self-hosting, you will need to set this
    160       configuration variable to a pattern for source code links. The value must be a string
    161       of the full URI to use for links with the following variables available for interpolation:
    162 
    163         * `%{path}`: the path of a file in the repo
    164         * `%{line}`: the line number in the file
    165 
    166       For GitLab/GitHub:
    167 
    168       ```text
    169       https://mydomain.org/user_or_team/repo_name/blob/main/%{path}#L%{line}
    170       ```
    171 
    172       For Bitbucket:
    173 
    174       ```text
    175       https://mydomain.org/user_or_team/repo_name/src/main/%{path}#cl-%{line}
    176       ```
    177 
    178   ## Groups
    179 
    180   ExDoc content can be organized in groups. This is done via the `:groups_for_extras`
    181   and `:groups_for_modules`. For example, imagine you are storing extra guides in
    182   your documentation which are organized per directory. In the extras section you
    183   have:
    184 
    185       extras: [
    186         "guides/introduction/foo.md",
    187         "guides/introduction/bar.md",
    188 
    189         ...
    190 
    191         "guides/advanced/baz.md",
    192         "guides/advanced/bat.md"
    193       ]
    194 
    195   You can have those grouped as follows:
    196 
    197       groups_for_extras: [
    198         "Introduction": Path.wildcard("guides/introduction/*.md"),
    199         "Advanced": Path.wildcard("guides/advanced/*.md")
    200       ]
    201 
    202   Or via a regex:
    203 
    204       groups_for_extras: [
    205         "Introduction": ~r"/introduction/",
    206         "Advanced": ~r"/advanced/"
    207       ]
    208 
    209   Similar can be done for modules:
    210 
    211       groups_for_modules: [
    212         "Data types": [Atom, Regex, URI],
    213         "Collections": [Enum, MapSet, Stream]
    214       ]
    215 
    216   A regex or the string name of the module is also supported.
    217 
    218   ### Grouping functions
    219 
    220   Functions inside a module can also be organized in groups. This is done via
    221   the `:groups_for_functions` configuration which is a keyword list of group
    222   titles and filtering functions that receive the documentation metadata of
    223   functions as argument.
    224 
    225   For example, imagine that you have an API client library with a large surface
    226   area for all the API endpoints you need to support. It would be helpful to
    227   group the functions with similar responsibilities together. In this case in
    228   your module you might have:
    229 
    230       defmodule APIClient do
    231         @doc section: :auth
    232         def refresh_token(params \\ [])
    233 
    234         @doc subject: :object
    235         def update_status(id, new_status)
    236 
    237         @doc permission: :grant
    238         def grant_privilege(resource, privilege)
    239       end
    240 
    241   And then in the configuration you can group these with:
    242 
    243       groups_for_functions: [
    244         Authentication: & &1[:section] == :auth,
    245         Resource: & &1[:subject] == :object,
    246         Admin: & &1[:permission] in [:grant, :write]
    247       ]
    248 
    249   A function can belong to a single group only. If multiple group filters match,
    250   the first will take precedence. Functions that don't have a custom group will
    251   be listed under the default "Functions" group.
    252 
    253   ## Additional JavaScript config
    254 
    255   Since version `0.20.0` ExDoc includes a way to enrich the documentation
    256   with new information without having to re-generate it, through a JavaScript
    257   file that can be shared across documentation for multiple versions of the
    258   package. If `:javascript_config_path` is set when building the documentation,
    259   this script will be referenced in each page's `<head>` using a `<script>` tag.
    260   The script should define data in global JavaScript variables that will be
    261   interpreted by `ex_doc` when viewing the documentation.
    262 
    263   Currently supported variables:
    264 
    265   ### `versionNodes`
    266 
    267   This global JavaScript variable should be providing an array of objects that
    268   define all versions of this Mix package which should appear in the package
    269   versions dropdown in the documentation sidebar. The versions dropdown allows
    270   for switching between package versions' documentation.
    271 
    272   Example:
    273 
    274   ```javascript
    275   var versionNodes = [
    276     {
    277       version: "v0.0.0", // version number or name (required)
    278       url: "https://hexdocs.pm/ex_doc/0.19.3/" // documentation URL (required)
    279     }
    280   ]
    281   ```
    282 
    283   ## Nesting
    284 
    285   ExDoc also allows module names in the sidebar to appear nested under a given
    286   prefix. The `:nest_modules_by_prefix` expects a list of module names, such as
    287   `[Foo.Bar, Bar.Baz]`. In this case, a module named `Foo.Bar.Baz` will appear
    288   nested within `Foo.Bar` and only the name `Baz` will be shown in the sidebar.
    289   Note the `Foo.Bar` module itself is not affected.
    290 
    291   This option is mainly intended to improve the display of long module names in
    292   the sidebar, particularly when they are too long for the sidebar or when many
    293   modules share a long prefix. If you mean to group modules logically or call
    294   attention to them in the docs, you should probably use `:groups_for_modules`
    295   (which can be used in conjunction with `:nest_modules_by_prefix`).
    296 
    297   ## Umbrella project
    298 
    299   ExDoc can be used in an umbrella project and generates a single documentation
    300   for all child apps. You can use the `:ignore_apps` configuration to exclude
    301   certain projects in the umbrella from documentation.
    302 
    303   Generating documentation per each child app can be achieved by running:
    304 
    305       mix cmd mix docs
    306 
    307   See `mix help cmd` for more information.
    308   """
    309 
    310   @switches [
    311     canonical: :string,
    312     formatter: :keep,
    313     language: :string,
    314     open: :boolean,
    315     output: :string,
    316     proglang: :string
    317   ]
    318 
    319   @aliases [
    320     f: :formatter,
    321     n: :canonical,
    322     o: :output
    323   ]
    324 
    325   @doc false
    326   def run(args, config \\ Mix.Project.config(), generator \\ &ExDoc.generate_docs/3) do
    327     {:ok, _} = Application.ensure_all_started(:ex_doc)
    328 
    329     unless Code.ensure_loaded?(ExDoc.Config) do
    330       Mix.raise(
    331         "Could not load ExDoc configuration. Please make sure you are running the " <>
    332           "docs task in the same Mix environment it is listed in your deps"
    333       )
    334     end
    335 
    336     {cli_opts, args, _} = OptionParser.parse(args, aliases: @aliases, switches: @switches)
    337 
    338     if args != [] do
    339       Mix.raise("Extraneous arguments on the command line")
    340     end
    341 
    342     project =
    343       to_string(
    344         config[:name] || config[:app] ||
    345           Mix.raise("expected :name or :app to be found in the project definition in mix.exs")
    346       )
    347 
    348     version = config[:version] || "dev"
    349 
    350     cli_opts =
    351       Keyword.update(cli_opts, :proglang, :elixir, fn proglang ->
    352         if proglang not in ~w(erlang elixir) do
    353           Mix.raise("--proglang must be elixir or erlang")
    354         end
    355 
    356         String.to_atom(proglang)
    357       end)
    358 
    359     options =
    360       config
    361       |> get_docs_opts()
    362       |> Keyword.merge(cli_opts)
    363       # accepted at root level config
    364       |> normalize_source_url(config)
    365       # accepted at root level config
    366       |> normalize_homepage_url(config)
    367       |> normalize_source_beam(config)
    368       |> normalize_apps(config)
    369       |> normalize_main()
    370       |> normalize_deps()
    371       |> put_package(config)
    372 
    373     Mix.shell().info("Generating docs...")
    374 
    375     for formatter <- get_formatters(options) do
    376       index = generator.(project, version, Keyword.put(options, :formatter, formatter))
    377       Mix.shell().info([:green, "View #{inspect(formatter)} docs at #{inspect(index)}"])
    378 
    379       if cli_opts[:open] do
    380         browser_open(index)
    381       end
    382 
    383       index
    384     end
    385   end
    386 
    387   defp get_formatters(options) do
    388     case Keyword.get_values(options, :formatter) do
    389       [] -> options[:formatters] || ["html", "epub"]
    390       values -> values
    391     end
    392   end
    393 
    394   defp get_docs_opts(config) do
    395     docs = config[:docs]
    396 
    397     cond do
    398       is_function(docs, 0) -> docs.()
    399       is_nil(docs) -> []
    400       true -> docs
    401     end
    402   end
    403 
    404   defp normalize_source_url(options, config) do
    405     if source_url = config[:source_url] do
    406       Keyword.put(options, :source_url, source_url)
    407     else
    408       options
    409     end
    410   end
    411 
    412   defp normalize_homepage_url(options, config) do
    413     if homepage_url = config[:homepage_url] do
    414       Keyword.put(options, :homepage_url, homepage_url)
    415     else
    416       options
    417     end
    418   end
    419 
    420   defp normalize_source_beam(options, config) do
    421     compile_path =
    422       if Mix.Project.umbrella?(config) do
    423         umbrella_compile_paths(Keyword.get(options, :ignore_apps, []))
    424       else
    425         Mix.Project.compile_path()
    426       end
    427 
    428     Keyword.put_new(options, :source_beam, compile_path)
    429   end
    430 
    431   defp umbrella_compile_paths(ignored_apps) do
    432     build = Mix.Project.build_path()
    433 
    434     for {app, _} <- Mix.Project.apps_paths(),
    435         app not in ignored_apps do
    436       Path.join([build, "lib", Atom.to_string(app), "ebin"])
    437     end
    438   end
    439 
    440   defp normalize_apps(options, config) do
    441     if Mix.Project.umbrella?(config) do
    442       ignore = Keyword.get(options, :ignore_apps, [])
    443 
    444       apps =
    445         for {app, _} <- Mix.Project.apps_paths(), app not in ignore do
    446           app
    447         end
    448 
    449       Keyword.put(options, :apps, apps)
    450     else
    451       Keyword.put(options, :apps, List.wrap(config[:app]))
    452     end
    453   end
    454 
    455   defp normalize_main(options) do
    456     main = options[:main]
    457 
    458     cond do
    459       is_nil(main) ->
    460         Keyword.delete(options, :main)
    461 
    462       is_atom(main) ->
    463         Keyword.put(options, :main, inspect(main))
    464 
    465       is_binary(main) ->
    466         options
    467     end
    468   end
    469 
    470   defp normalize_deps(options) do
    471     user_deps = Keyword.get(options, :deps, [])
    472 
    473     deps =
    474       for {app, doc} <- Keyword.merge(get_deps(), user_deps),
    475           lib_dir = :code.lib_dir(app),
    476           is_list(lib_dir),
    477           do: {app, doc}
    478 
    479     Keyword.put(options, :deps, deps)
    480   end
    481 
    482   defp get_deps do
    483     for {key, _} <- Mix.Project.deps_paths(),
    484         _ = Application.load(key),
    485         vsn = Application.spec(key, :vsn) do
    486       {key, "https://hexdocs.pm/#{key}/#{vsn}/"}
    487     end
    488   end
    489 
    490   defp put_package(options, config) do
    491     if package = config[:package] do
    492       Keyword.put(options, :package, package[:name] || config[:app])
    493     else
    494       options
    495     end
    496   end
    497 
    498   defp browser_open(path) do
    499     {cmd, args, options} =
    500       case :os.type() do
    501         {:win32, _} ->
    502           dirname = Path.dirname(path)
    503           basename = Path.basename(path)
    504           {"cmd", ["/c", "start", basename], [cd: dirname]}
    505 
    506         {:unix, :darwin} ->
    507           {"open", [path], []}
    508 
    509         {:unix, _} ->
    510           {"xdg-open", [path], []}
    511       end
    512 
    513     System.cmd(cmd, args, options)
    514   end
    515 end