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