zf

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

autolink.ex (5860B)


      1 defmodule ExDoc.Autolink do
      2   @moduledoc false
      3 
      4   # * `:apps` - the apps that the docs are being generated for. When linking modules they are
      5   #   checked if they are part of the app and based on that the links are relative or absolute.
      6   #
      7   # * `:current_module` - the module that the docs are being generated for. Used to link local
      8   #   calls and see if remote calls are in the same app.
      9   #
     10   # * `:module_id` - id of the module being documented (e.g.: `"String"`)
     11   #
     12   # * `:file` - source file location
     13   #
     14   # * `:line` - line number of the beginning of the documentation
     15   #
     16   # * `:id` - a module/function/etc being documented (e.g.: `"String.upcase/2"`)
     17   #
     18   # * `:ext` - the extension (`".html"`, "`.xhtml"`, etc)
     19   #
     20   # * `:extras` - map of extras
     21   #
     22   # * `:skip_undefined_reference_warnings_on` - list of modules to skip the warning on
     23 
     24   defstruct [
     25     :current_module,
     26     :module_id,
     27     :id,
     28     :line,
     29     file: "nofile",
     30     apps: [],
     31     extras: [],
     32     deps: [],
     33     ext: ".html",
     34     siblings: [],
     35     skip_undefined_reference_warnings_on: []
     36   ]
     37 
     38   @hexdocs "https://hexdocs.pm/"
     39   @otpdocs "https://www.erlang.org/doc/man/"
     40 
     41   def app_module_url(:ex_doc, module, %{current_module: module} = config) do
     42     path = module |> inspect() |> String.trim_leading(":")
     43     ex_doc_app_url(module, config, path, config.ext, "#content")
     44   end
     45 
     46   def app_module_url(:ex_doc, module, config) do
     47     path = module |> inspect() |> String.trim_leading(":")
     48     ex_doc_app_url(module, config, path, config.ext, "")
     49   end
     50 
     51   def app_module_url(:otp, module, _config) do
     52     @otpdocs <> "#{module}.html"
     53   end
     54 
     55   def app_module_url(:no_tool, _, _) do
     56     nil
     57   end
     58 
     59   # TODO: make more generic
     60   @doc false
     61   def ex_doc_app_url(module, config, path, ext, suffix) do
     62     if app = app(module) do
     63       if app in config.apps do
     64         path <> ext <> suffix
     65       else
     66         config.deps
     67         |> Keyword.get_lazy(app, fn -> @hexdocs <> "#{app}" end)
     68         |> String.trim_trailing("/")
     69         |> Kernel.<>("/" <> path <> ".html" <> suffix)
     70       end
     71     else
     72       path <> ext <> suffix
     73     end
     74   end
     75 
     76   defp app(module) do
     77     {_, app} = app_info(module)
     78     app
     79   end
     80 
     81   @doc false
     82   def tool(module, config) do
     83     if match?("Elixir." <> _, Atom.to_string(module)) do
     84       :ex_doc
     85     else
     86       {otp, app} = app_info(module)
     87       apps = Enum.uniq(config.apps ++ Keyword.keys(config.deps))
     88 
     89       if otp == true and app not in apps do
     90         :otp
     91       else
     92         :ex_doc
     93       end
     94     end
     95   end
     96 
     97   defp app_info(module) do
     98     case :code.which(module) do
     99       :preloaded ->
    100         {true, :erts}
    101 
    102       maybe_path ->
    103         otp? = is_list(maybe_path) and List.starts_with?(maybe_path, :code.lib_dir())
    104 
    105         app =
    106           case :application.get_application(module) do
    107             {:ok, app} ->
    108               app
    109 
    110             _ ->
    111               with true <- is_list(maybe_path),
    112                    [_, "ebin", app, "lib" | _] <- maybe_path |> Path.split() |> Enum.reverse() do
    113                 String.to_atom(app)
    114               else
    115                 _ -> nil
    116               end
    117           end
    118 
    119         {otp?, app}
    120     end
    121   end
    122 
    123   def maybe_warn(ref, config, visibility, metadata) do
    124     skipped = config.skip_undefined_reference_warnings_on
    125     file = Path.relative_to(config.file, File.cwd!())
    126     line = config.line
    127 
    128     unless Enum.any?([config.id, config.module_id, file], &(&1 in skipped)) do
    129       warn(ref, {file, line}, config.id, visibility, metadata)
    130     end
    131   end
    132 
    133   defp warn(message, {file, line}, id) do
    134     warning = IO.ANSI.format([:yellow, "warning: ", :reset])
    135 
    136     stacktrace =
    137       "  #{file}" <>
    138         if(line, do: ":#{line}", else: "") <>
    139         if(id, do: ": #{id}", else: "")
    140 
    141     IO.puts(:stderr, [warning, message, ?\n, stacktrace, ?\n])
    142   end
    143 
    144   defp warn(ref, file_line, id, visibility, metadata)
    145 
    146   defp warn(
    147          {:module, _module},
    148          {file, line},
    149          id,
    150          visibility,
    151          %{mix_task: true, original_text: original_text}
    152        ) do
    153     message =
    154       "documentation references \"#{original_text}\" but it is " <>
    155         format_visibility(visibility, :module)
    156 
    157     warn(message, {file, line}, id)
    158   end
    159 
    160   defp warn(
    161          {:module, _module},
    162          {file, line},
    163          id,
    164          visibility,
    165          %{original_text: original_text}
    166        ) do
    167     message =
    168       "documentation references module \"#{original_text}\" but it is " <>
    169         format_visibility(visibility, :module)
    170 
    171     warn(message, {file, line}, id)
    172   end
    173 
    174   defp warn(
    175          nil,
    176          {file, line},
    177          id,
    178          _visibility,
    179          %{file_path: _file_path, original_text: original_text}
    180        ) do
    181     message = "documentation references file \"#{original_text}\" but it does not exist"
    182 
    183     warn(message, {file, line}, id)
    184   end
    185 
    186   defp warn(
    187          {kind, _module, _name, _arity},
    188          {file, line},
    189          id,
    190          visibility,
    191          %{original_text: original_text}
    192        ) do
    193     message =
    194       "documentation references #{kind} \"#{original_text}\" but it is " <>
    195         format_visibility(visibility, kind)
    196 
    197     warn(message, {file, line}, id)
    198   end
    199 
    200   defp warn(message, {file, line}, id, _, _) when is_binary(message) do
    201     warn(message, {file, line}, id)
    202   end
    203 
    204   # there is not such a thing as private callback or private module
    205   defp format_visibility(visibility, kind) when kind in [:module, :callback], do: "#{visibility}"
    206 
    207   # typep is defined as :hidden, since there is no :private visibility value
    208   # but type defined with @doc false also is the stored the same way.
    209   defp format_visibility(:hidden, :type), do: "hidden or private"
    210 
    211   # for the rest, it can either be undefined or private
    212   defp format_visibility(:undefined, _kind), do: "undefined or private"
    213   defp format_visibility(visibility, _kind), do: "#{visibility}"
    214 end