language_colours/lib/language_colours.ex
2021-09-03 21:51:45 +03:00

177 lines
4.9 KiB
Elixir

defmodule LanguageColours do
@moduledoc """
LanguageColours offers an API for retrieving colours for programming languages based on GitHub
colour data (or some other dataset).
```iex
iex> LanguageColours.get("Ada", :my_db)
"#02f88c"
```
To install, add LanguageColours as a dependency of your Mix project:
```elixir
defp deps do
[
{:language_colours, "~> 1.0.0"},
...
]
end
```
## Databases
LanguageColours can be served by different databases. Databases must conform to the
`LanguageColours.Database` behaviour. There is one database included, the
`LanguageColours.ETSDatabase`, that uses ETS to store the language colour data. As a source, it
uses a JSON file in the format found in the https://github.com/ozh/github-colors/ repo. This
file can be downloaded in a development environment with `mix language.colours.dl.data`.
## Fallback colours
If a language is not found in the database, `nil` will be returned by default. Optionally, the
[Rainbow](https://hex.pm/packages/rainbow) package can be used to generate a random colour based
on the language name as fallback. To use the fallback, set the database configuration option
`:fallback` to `true`, and install Rainbow as a dependency:
```elixir
defp deps do
[
{:rainbow, "~> 0.1.0"},
...
]
end
```
## Configuration
To configure a database, you can give the configuration directly in the function call:
```elixir
config = %{...}
LanguageColours.get("C++", config)
```
Or you can configure database aliases in your favourite config file (`runtime.exs` is
recommended so you don't need to recompile the app on config changes):
```elixir
# Config file
config :language_colours,
databases: %{
foo_db: %{
...
}
}
```
Then you can use this name in the function call:
```elixir
LanguageColours.get("COBOL", :foo_db)
```
The supported configuration options are:
* `:json_parser`: Module name of the JSON parser to use. Default is
[Jason](https://hex.pm/packages/jason).
* `:databases`: An atom-keyed map of database specific configurations, which can be maps
themselves or keyword lists.
The database specific configuration options are:
* `:db`: The database module to use. This option is required.
* `:fallback`: Boolean specifying whether to get a fallback colour from Rainbow or not, if
language was not found in database. Default `false`.
Additionally, the ETSDatabase has its own database specific options:
* `:file`: Path to the file to read the JSON data from.
* `:server_name`: The name to use for the database process. Must conform to
`t:GenServer.name/0`. By default the module name is used.
* `:ets_table`: Name of the ETS table to use.
## Setting up using ETSDatabase
First configure your database. Then start the database in your supervisor using the
`LanguageColours.ETSDatabase.startup_options/1` function like this:
```elixir
config = LanguageColours.config!(:foo_db)
children = [
...,
{LanguageColours.ETSDatabase, LanguageColours.ETSDatabase.startup_options(config)},
...
]
```
Alternatively you can directly give the `t:LanguageColours.ETSDatabase.Options.t/0` struct as
the argument.
"""
@doc """
Get the colour corresponding to the given language.
The database specified in the second argument (as configuration or preconfigured database name)
is used to fetch the language. If the language is not found and fallback is not configured,
`nil` is returned.
"""
@spec get(String.t(), atom() | keyword() | map()) :: LanguageColours.Colour.t() | nil
def get(language, db_or_config)
def get(language, db) when is_atom(db) do
config = config!(db)
get(language, config)
end
def get(language, config) when is_list(config) do
get(language, Map.new(config))
end
def get(language, config) when is_map(config) do
colour = config.db.get(language, config)
if is_nil(colour) && Map.get(config, :fallback) do
Rainbow.colorize(language, format: "hexcolor")
else
colour
end
end
@doc """
Update the database specified in the argument.
The argument may be a preconfigured database name or a configuration map/kwlist.
"""
@spec update(atom() | keyword() | map()) :: :ok
def update(db_or_config)
def update(db) when is_atom(db) do
config = config!(db)
update(config)
end
def update(config) when is_list(config) do
update(Map.new(config))
end
def update(config) when is_map(config) do
config.db.update(config)
end
@doc """
Retrieve the configuration for the given database name.
If the database is not configured, an error will be raised.
"""
@spec config!(atom()) :: map()
def config!(db) do
conf =
Application.get_env(:language_colours, :databases) ||
raise "LanguageColours databases not configured!"
Map.get(conf, db) || raise "Configuration for #{inspect(db)} colour database not found!"
end
end