1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
defmodule Makeup.Registry do
@moduledoc """
A registry that allows users to dynamically register new makeup lexers.
Lexers should register themselves on application start.
That way, you can add support for new programming languages by depending on the relevant lexers.
This is useful for projects such as ExDoc, which might contain code
in a number of different programming languages.
"""
@name_registry_key :lexer_name_registry
@extension_registry_key :lexer_extension_registry
# --------------------------------------------------------------------------
# Public API
# --------------------------------------------------------------------------
@doc """
Gets the list of supported language names.
"""
def supported_language_names() do
Map.keys(get_name_registry())
end
@doc """
Gets the list of supported language extensions.
"""
def supported_file_extensions() do
Map.keys(get_extension_registry())
end
@doc """
Adds a new lexer to Makeup's registry under the given `name`.
This function expects a language name (e.g. `"elixir"`) and a pair containing
a `lexer` and a list of `options`.
You might want to use the `Makeup.Registry.register_lexer/2` function instead.
## Examples
alias Makeup.Lexers.ElixirLexer
alias Makeup.Registry
Registry.register_lexer_with_name("elixir", {ElixirLexer, []})
Registry.register_lexer_with_name("iex", {ElixirLexer, []})
"""
def register_lexer_with_name(name, {lexer, options}) when is_binary(name) do
old_registry = get_name_registry()
updated_registry = Map.put(old_registry, name, {lexer, options})
put_name_registry(updated_registry)
end
@doc """
Adds a new lexer to Makeup's registry under the given `extension`.
This function expects a file extension (e.g. `"ex"`) and a pair containing
a `lexer` and a list of `options`.
You might want to use the `Makeup.Registry.register_lexer/2` function instead.
## Examples
alias Makeup.Lexers.ElixirLexer
alias Makeup.Registry
Registry.register_lexer_with_extension("ex"), {ElixirLexer, []})
Registry.register_lexer_with_extension("exs"), {ElixirLexer, []})
"""
def register_lexer_with_extension(name, {lexer, options}) when is_binary(name) do
old_registry = get_extension_registry()
updated_registry = Map.put(old_registry, name, {lexer, options})
put_extension_registry(updated_registry)
end
@doc """
Add a new lexer to Makeup's registry under the given names and extensions.
Expects a lexer `lexer` and a number of options:
* `:options` (default: `[]`) - the lexer options.
If your lexer doesn't take any options, you'll want the default value of `[]`.
* `:names` (default: `[]`) - a list of strings with the language names for the lexer.
Language names are strings, not atoms.
Even if there is only one valid name, you must supply a list with that name.
To avoid filling the registry unnecessarily, you should normalize your language names
to lowercase strings.
If the caller wants to support upper case language names for some reason,
they can normalize the language names themselves.
* `:extensions` (default: `[]`) - the list of file extensions for the languages supported by the lexer.
For example, the elixir lexer should support the `"ex"` and `"exs"` file extensions.
The extensions should not include the dot.
That is, you should register `"ex"` and not `".ex"`.
Even if there is only a supported extension, you must supply a list.
## Example
alias Makeup.Registry
alias Makeup.Lexers.ElixirLexer
# The `:options` key is not required
Registry.register_lexer(ElixirLexer, names: ["elixir", "iex"], extensions: ["ex", "exs"])
"""
def register_lexer(lexer, opts) do
options = Keyword.get(opts, :options, [])
names = Keyword.get(opts, :names, [])
extensions = Keyword.get(opts, :extensions, [])
# Associate the lexer with the names
for name <- names, do: register_lexer_with_name(name, {lexer, options})
# Associate the lexer with the extensions
for extension <- extensions, do: register_lexer_with_extension(extension, {lexer, options})
end
@doc """
Fetches the lexer from Makeup's registry with the given `name`.
Returns either `{:ok, {lexer, options}}` or `:error`.
This behaviour is based on `Map.fetch/2`.
"""
def fetch_lexer_by_name(name) do
Map.fetch(get_name_registry(), name)
end
@doc """
Fetches the lexer from Makeup's registry with the given `name`.
Returns either `{lexer, options}` or raises a `KeyError`.
This behaviour is based on `Map.fetch!/2`.
"""
def fetch_lexer_by_name!(name) do
Map.fetch!(get_name_registry(), name)
end
@doc """
Gets the lexer from Makeup's registry with the given `name`.
Returns either `{lexer, options}` or the `default` value
(which by default is `nil`).
This behaviour is based on `Map.get/3`.
"""
def get_lexer_by_name(name, default \\ nil) do
Map.get(get_name_registry(), name, default)
end
@doc """
Fetches a lexer from Makeup's registry with the given file `extension`.
Returns either `{:ok, {lexer, options}}` or `:error`.
This behaviour is based on `Map.fetch/2`.
"""
def fetch_lexer_by_extension(name) do
Map.fetch(get_extension_registry(), name)
end
@doc """
Fetches the lexer from Makeup's registry with the given file `extension`.
Returns either `{:ok, {lexer, options}}` or raises a `KeyError`.
This behaviour is based on `Map.fetch/2`.
"""
def fetch_lexer_by_extension!(name) do
Map.fetch!(get_extension_registry(), name)
end
@doc """
Gets the lexer from Makeup's registry with the given file `extension`.
Returns either `{lexer, options}` or the `default` value
(which by default is `nil`).
This behaviour is based on `Map.get/3`.
"""
def get_lexer_by_extension(name, default \\ nil) do
Map.get(get_extension_registry(), name, default)
end
# ---------------------------------------------------------------------------
# Functions not meant to be used outside Makeup
# ---------------------------------------------------------------------------
# This functions are meant to be run on application startup
# or to be used as helpers in Makeup's internal tests.
# They are not meant to be invoked by users of Makeup
@doc false
def create_name_registry() do
Application.put_env(:makeup, @name_registry_key, %{})
end
@doc false
def create_extension_registry() do
Application.put_env(:makeup, @extension_registry_key, %{})
end
# The `clean_*_registry` are actually the same as the `create_*_registry`,
# but that's because of implementation details, so it makes sense to have
# separate groups of functions
@doc false
def clean_name_registry() do
put_name_registry(%{})
end
@doc false
def clean_extension_registry() do
put_extension_registry(%{})
end
# ----------------------------------------------------------------------------
# Private helper functions
# ----------------------------------------------------------------------------
defp get_name_registry() do
Application.get_env(:makeup, @name_registry_key)
end
defp put_name_registry(registry) do
Application.put_env(:makeup, @name_registry_key, registry)
end
defp get_extension_registry() do
Application.get_env(:makeup, @extension_registry_key)
end
defp put_extension_registry(registry) do
Application.put_env(:makeup, @extension_registry_key, registry)
end
end
|