Current section
Files
Jump to
Current section
Files
lib/exts.ex
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# Version 2, December 2004
#
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
# 0. You just DO WHAT THE FUCK YOU WANT TO.
defmodule Exts do
@type table :: integer | atom
alias Exts.FileError
alias Exts.Selection
alias Exts.Select
alias Exts.Match
@doc """
Load a table from the given file, see `ets:file2tab`.
"""
@spec load(String.t) :: { :ok, table } | { :error, any }
@spec load(String.t, Keyword.t) :: { :ok, table } | { :error, any }
def load(path, options \\ []) when path |> is_binary do
:ets.file2tab(List.to_string(path), options)
end
@doc """
Load a table from the given file, raising if there's any problem doing so,
see `ets:file2tab`.
"""
@spec load!(String.t) :: table | no_return
@spec load!(String.t, Keyword.t) :: table | no_return
def load!(path, options \\ []) do
case load(path, options) do
{ :ok, table } ->
table
{ :error, reason } ->
raise FileError, reason: reason
end
end
@doc """
Dump the given table to the given file, see `ets:tab2file`.
"""
@spec dump(table, String.t) :: :ok | { :error, any }
@spec dump(table, String.t, Keyword.t) :: :ok | { :error, any }
def dump(table, path, options \\ []) when path |> is_binary do
:ets.tab2file(table, List.to_string(path), options)
end
@doc """
Dump the given table to the given file, raising if there's any problem doing
so, see `ets:tab2file`.
"""
@spec dump!(table, String.t) :: :ok | no_return
@spec dump!(table, String.t, Keyword.t) :: :ok | no_return
def dump!(table, path, options \\ []) do
case dump(table, path, options) do
:ok ->
:ok
{ :error, reason } ->
raise FileError, reason: reason
end
end
@doc """
Get information about the given table or table saved on file, see `ets:info`
and `ets:tabfile_info`.
"""
@spec info(String.t | table) :: { :ok, any } | { :error, any } | Keyword.t | nil
def info(path) when path |> is_binary do
:ets.tabfile_info(List.to_string(path))
end
def info(table) do
case :ets.info(table) do
:undefined -> nil
value -> value
end
end
@doc """
Get specific information about the given table and given topic, see
`ets:info`.
"""
@spec info(table, atom) :: any | nil
def info(table, key) do
case :ets.info(table, key) do
:undefined -> nil
value -> value
end
end
@doc """
Rename the given table to the given name, see `ets:rename`.
"""
@spec rename(table, atom) :: atom
def rename(table, name) do
:ets.rename(table, name)
end
@doc """
Get all the present tables, see `ets:all`.
"""
@spec all :: [table]
def all do
:ets.all
end
@doc """
Convert the given table to a list of terms, see `ets:tab2list`.
"""
@spec to_list(table) :: [term]
def to_list(table) do
:ets.tab2list(table)
end
@doc """
Create a new unnamed table with the default options, see `ets:new`.
"""
@spec new :: table
def new do
:ets.new(nil, [])
end
@doc """
Create a new unnamed table with the given options, see `ets:new`.
"""
@spec new(Keyword.t) :: table
def new(options) do
new(nil, options)
end
@doc """
Create a new named table with the given options, see `ets:new`.
## Options
* `:index` sets the position of the key in the tuple, default is 0.
* `:concurrency` can be either `:both`, `:write` or `:read`, it sets
`:write_concurrency` and `:read_concurrency` appropriately.
* `:type` can either be `:set`, `:ordered_set`, `:bag` or `:duplicate_bag`,
default is `:set`
* `:access` can either be `:public`, `:protected`, `:private`, default is
`:protected`.
* `:heir` sets a heir for the table, see the documentation of `ets:new`,
default is none.
* `:compressed` can be either true or false, default is false
"""
@spec new(atom, Keyword.t) :: table
def new(name, options) do
options = options
|> Keyword.put(:name, name)
|> Keyword.put_new(:index, 0)
|> Keyword.put_new(:heir, nil)
|> Keyword.put_new(:compressed, false)
:ets.new(name, Enum.flat_map(options, fn
{ :name, nil } ->
[]
{ :name, _ } ->
[:named_table]
{ :index, value } ->
[{ :keypos, value + 1 }]
{ :concurrency, :both } ->
[{ :write_concurrency, true }, { :read_concurrency, true }]
{ :concurrency, :write } ->
[{ :write_concurrency, true }, { :read_concurrency, false }]
{ :concurrency, :read } ->
[{ :write_concurrency, false }, { :read_concurrency, true }]
{ :type, :set } ->
[:set]
{ :type, :ordered_set } ->
[:ordered_set]
{ :type, :bag } ->
[:bag]
{ :type, :duplicate_bag } ->
[:duplicate_bag]
{ :access, :public } ->
[:public]
{ :access, :protected } ->
[:protected]
{ :access, :private } ->
[:private]
{ :heir, nil } ->
[{ :heir, :none }]
{ :heir, value } ->
[{ :heir, value[:pid], value[:data] }]
{ :compressed, true } ->
[:compressed]
{ :compressed, false } ->
[]
end))
end
@doc """
Give the given table to the given process, see `ets:give_away`.
"""
@spec give_to(table, pid) :: true
@spec give_to(table, pid, any) :: true
def give_to(table, pid, data \\ nil) do
:ets.give_away(table, pid, data)
end
@doc """
Clear the given table, see `ets:delete_all_objects`.
"""
@spec clear(table) :: true
def clear(table) do
:ets.delete_all_objects(table)
end
@doc """
Destroy the given table, see `ets:delete`.
"""
@spec destroy(table) :: true
def destroy(table) do
:ets.delete(table)
end
@doc """
Read the given term from the given table, see `ets:lookup`.
"""
@spec read(table, any) :: [term]
def read(table, key) do
:ets.lookup(table, key)
end
@doc """
Read the terms in the given slot, see `ets:slot`.
"""
@spec at(table, integer) :: [term]
def at(table, slot) do
case :ets.slot(table, slot) do
:'$end_of_table' -> nil
r -> r
end
end
@doc """
Get the first key in the given table, see `ets:first`.
"""
@spec first(table) :: any | nil
def first(table) do
case :ets.first(table) do
:"$end_of_table" -> nil
key -> key
end
end
@doc """
Get the next key in the given table, see `ets:next`.
"""
@spec next(table, any) :: any | nil
def next(table, key) do
case :ets.next(table, key) do
:"$end_of_table" -> nil
key -> key
end
end
@doc """
Get the previous key in the given table, see `ets:prev`.
"""
@spec prev(table, any) :: any | nil
def prev(table, key) do
case :ets.prev(table, key) do
:"$end_of_table" -> nil
key -> key
end
end
@doc """
Get the last key in the given table, see `ets:prev`.
"""
@spec last(table) :: any | nil
def last(table) do
case :ets.last(table) do
:"$end_of_table" -> nil
key -> key
end
end
@doc """
Get the keys in the given table.
"""
@spec keys(table) :: [term]
def keys(table) do
do_keys([], table, first(table))
end
defp do_keys(acc, _, nil) do
acc
end
defp do_keys(acc, table, key) do
[key | acc] |> do_keys(table, next(table, key))
end
@doc """
Select terms in the given table using a match_spec, see `ets:select`.
"""
@spec select(table, any, non_neg_integer) :: Selection.t | nil
def select(table, match_spec, options \\ [])
def select(table, match_spec, []) do
Select.new(:ets.select(table, match_spec))
end
def select(table, match_spec, limit: limit) do
Select.new(:ets.select(table, match_spec, limit))
end
@doc """
Select terms in the given table using a match_spec, traversing in reverse,
see `ets:select_reverse`.
"""
@spec reverse_select(table, any) :: Selection.t | nil
def reverse_select(table, match_spec, options \\ [])
def reverse_select(table, match_spec, []) do
Select.new(:ets.select_reverse(table, match_spec), true)
end
def reverse_select(table, match_spec, limit: limit) do
Select.new(:ets.select_reverse(table, match_spec, limit), true)
end
@doc """
Match terms from the given table with the given pattern, see `ets:match`.
"""
@spec match(table, any) :: Selection.t | nil
def match(table, pattern) do
Match.new(:ets.match(table, pattern))
end
@doc """
Match terms from the given table with the given pattern and options, see
`ets:match`.
## Options
* `:whole` when true it returns the whole term.
* `:delete` when true it deletes the matching terms instead of returning
them.
* `:limit` the amount of elements to select at a time.
"""
@spec match(table, any | integer, Keyword.t | any) :: Selection.t | nil
def match(table, pattern, delete: true) do
:ets.match_delete(table, pattern)
end
def match(table, pattern, whole: true) do
Match.new(:ets.match_object(table, pattern))
end
def match(table, pattern, limit: limit) do
Match.new(:ets.match(table, pattern, limit))
end
def match(table, pattern, limit: limit, whole: true) do
Match.new(:ets.match_object(table, pattern, limit))
end
@doc """
Get the number of terms in the given table.
"""
@spec count(table) :: non_neg_integer
def count(table) do
info(table, :size)
end
@doc """
Count the number of terms matching the match_spec, see `ets:select_count`.
"""
@spec count(table, any) :: non_neg_integer
def count(table, match_spec) do
:ets.select_count(table, match_spec)
end
@doc """
Fold the given table from the left, see `ets:foldl`.
"""
@spec foldl(table, any, (term, any -> any)) :: any
def foldl(table, acc, fun) do
:ets.foldl(fun, acc, table)
end
@doc """
Fold the given table from the right, see `ets:foldr`.
"""
@spec foldr(table, any, (term, any -> any)) :: any
def foldr(table, acc, fun) do
:ets.foldr(fun, acc, table)
end
@doc """
Delete the term matching the given pattern or key in the given table, see
`ets:select_delete` and `ets:delete`.
"""
@spec delete(table, any) :: true | integer
def delete(table, [{ _, _, _ } | _] = match_spec) do
:ets.select_delete(table, match_spec)
end
def delete(table, key) do
:ets.delete(table, key)
end
@doc """
Delete the given term from the given table, see `ets:delete_object`.
"""
@spec delete!(table, term) :: true
def delete!(table, object) do
:ets.delete_object(table, object)
end
# TODO: udpate_counter and update_element
@doc """
Write the given term to the given table optionally disabling overwriting,
see `ets:insert` and `ets:insert_new`.
"""
@spec write(table, term) :: boolean
@spec write(table, term, Keyword.t) :: boolean
def write(table, object, options \\ []) do
if options[:overwrite] == false do
:ets.insert_new(table, object)
else
:ets.insert(table, object)
end
end
end