zf

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

refs.ex (4778B)


      1 defmodule ExDoc.Refs do
      2   @moduledoc false
      3 
      4   # A read-through cache of documentation references.
      5   #
      6   # A given ref is always associated with a module. If we don't have a ref
      7   # in the cache we fetch the module's docs chunk and fill in the cache.
      8   #
      9   # If the module does not have the docs chunk, we fetch it's functions,
     10   # callbacks and types from other sources.
     11 
     12   @typep entry() :: {ref(), visibility()}
     13 
     14   @typep ref() ::
     15            {:module, module()}
     16            | {kind(), module(), name :: atom(), arity()}
     17   @typep kind() :: :function | :callback | :type
     18   @typep visibility() :: :hidden | :public | :undefined | :partial
     19 
     20   @name __MODULE__
     21 
     22   use GenServer
     23 
     24   @spec start_link(any()) :: GenServer.on_start()
     25   def start_link(arg) do
     26     GenServer.start_link(__MODULE__, arg, name: @name)
     27   end
     28 
     29   @spec init(any()) :: {:ok, nil}
     30   def init(_) do
     31     :ets.new(@name, [:named_table, :public, :set])
     32     {:ok, nil}
     33   end
     34 
     35   @spec clear() :: :ok
     36   def clear() do
     37     :ets.delete_all_objects(@name)
     38     :ok
     39   end
     40 
     41   @spec get_visibility(ref()) :: visibility()
     42   def get_visibility(ref) do
     43     case lookup(ref) do
     44       {:ok, visibility} ->
     45         visibility
     46 
     47       :error ->
     48         case fetch(ref) do
     49           {:ok, visibility} -> visibility
     50           :error -> :undefined
     51         end
     52     end
     53   end
     54 
     55   @spec insert([entry()]) :: :ok
     56   def insert(entries) do
     57     true = :ets.insert(@name, entries)
     58     :ok
     59   end
     60 
     61   @spec insert_from_chunk(module, tuple()) :: :ok
     62   def insert_from_chunk(module, result) do
     63     module
     64     |> fetch_entries(result)
     65     |> insert()
     66 
     67     :ok
     68   end
     69 
     70   defp lookup(ref) do
     71     case :ets.lookup(@name, ref) do
     72       [{^ref, visibility}] -> {:ok, visibility}
     73       [] -> :error
     74     end
     75   rescue
     76     _ -> :error
     77   end
     78 
     79   defp fetch({:module, module} = ref) do
     80     insert_from_chunk(module, Code.fetch_docs(module))
     81     lookup(ref)
     82   end
     83 
     84   defp fetch({_kind, module, _name, _arity} = ref) do
     85     get_visibility({:module, module})
     86     lookup(ref)
     87   end
     88 
     89   defp fetch_entries(module, result) do
     90     case result do
     91       {:docs_v1, _, _, _, module_doc, _, docs} ->
     92         module_visibility = visibility(module_doc)
     93 
     94         [{{:module, module}, module_visibility}] ++
     95           for {{kind, name, arity}, _, _, doc, metadata} <- docs,
     96               ref_kind = to_ref_kind(kind),
     97               visibility = visibility(module_doc, {ref_kind, name, doc}),
     98               arity <- (arity - (metadata[:defaults] || 0))..arity do
     99             {{ref_kind, module, name, arity}, visibility}
    100           end
    101 
    102       {:error, _} ->
    103         if Code.ensure_loaded?(module) do
    104           # We say it is limited because the types may not actually be available in the beam file.
    105           [{{:module, module}, :limited}] ++
    106             to_refs(exports(module), module, :function) ++
    107             to_refs(callbacks(module), module, :callback) ++
    108             to_refs(types(module, [:type, :opaque]), module, :type)
    109         else
    110           [{{:module, module}, :undefined}]
    111         end
    112     end
    113   end
    114 
    115   defguardp has_no_docs(doc) when doc == :none or doc == %{}
    116 
    117   defp starts_with_underscore?(name), do: hd(Atom.to_charlist(name)) == ?_
    118 
    119   defp visibility(:hidden),
    120     do: :hidden
    121 
    122   defp visibility(_module_doc),
    123     do: :public
    124 
    125   defp visibility(_module_doc, {kind, _name, _doc})
    126        when kind not in [:callback, :function, :type],
    127        do: raise(ArgumentError, "Unknown kind #{inspect(kind)}")
    128 
    129   defp visibility(:hidden, {_kind, _name, _doc}),
    130     do: :hidden
    131 
    132   defp visibility(_, {_kind, _name, :hidden}),
    133     do: :hidden
    134 
    135   defp visibility(_, {kind, name, doc}) when has_no_docs(doc) do
    136     cond do
    137       kind in [:callback, :type] ->
    138         :public
    139 
    140       kind == :function and starts_with_underscore?(name) ->
    141         :hidden
    142 
    143       kind == :function ->
    144         :public
    145     end
    146   end
    147 
    148   defp visibility(_, {_, _, _}) do
    149     :public
    150   end
    151 
    152   defp to_ref_kind(:macro), do: :function
    153   defp to_ref_kind(:macrocallback), do: :callback
    154   defp to_ref_kind(other), do: other
    155 
    156   defp exports(module) do
    157     if function_exported?(module, :__info__, 1) do
    158       module.__info__(:functions) ++ module.__info__(:macros)
    159     else
    160       module.module_info(:exports)
    161     end
    162   end
    163 
    164   defp callbacks(module) do
    165     if function_exported?(module, :behaviour_info, 1) do
    166       module.behaviour_info(:callbacks)
    167     else
    168       []
    169     end
    170   end
    171 
    172   defp types(module, kind_list) do
    173     case Code.Typespec.fetch_types(module) do
    174       {:ok, list} ->
    175         for {kind, {name, _, args}} <- list,
    176             kind in kind_list do
    177           {name, length(args)}
    178         end
    179 
    180       :error ->
    181         []
    182     end
    183   end
    184 
    185   defp to_refs(list, module, kind, visibility \\ :public) do
    186     for {name, arity} <- list do
    187       {{kind, module, name, arity}, visibility}
    188     end
    189   end
    190 end