Current section
Files
Jump to
Current section
Files
lib/radius_dict.ex
defmodule RadiusDict do
require GenServer
require Logger
def init(file) do
init_ets()
load(file)
{:ok,%{file: file}}
end #init/1
#def handle_call(:reload,_from,state) do
# ctx = load(state.file)
# {:reply, {:ok,ctx}, state}
#end #handle_call/3
#exports
def start_link(file) do
GenServer.start_link(__MODULE__,file, name: __MODULE__)
end #start_link/1
#def reload() do
# GenServer.call(__MODULE__,:reload)
#end #reload
defmodule EntryNotFoundError do
defexception [:type,:key]
def message(e) do
"Enrty #{e.type} not found for key: #{inspect e.key}"
end
end
defmodule DictEntry do
defmacro __using__(_) do
quote do
def init! do
DictEntry.__init__(__MODULE__)
end
def insert(val) do
DictEntry.__insert__(__MODULE__,val)
end
def lookup(key) do
try do
lookup!(key)
rescue
e in EntryNotFoundError -> []
end
end
def lookup!(key) do
DictEntry.__lookup__(__MODULE__,key)
end
def on_insert(val) do
val
end
def on_load(val) do
val
end
defoverridable [ on_insert: 1, on_load: 1 ]
end
end
def __init__(mod) do
:ets.new mod, [:set, :protected, :named_table]
end
def __insert__(mod,val) do
val = mod.on_insert val
keys = val|> mod.index_for
objs = Enum.map keys, fn(x) ->
if not :ets.insert_new mod,{x,val} do
#Logger.warn "ignored duplicat #{mod}: #{inspect x} at #{val.file}:#{val.line} \nObject: #{inspect val, pretty: true}"
:ok
end
end
end
def __lookup__(mod,[key]) do
__lookup__ mod,key
end
def __lookup__(mod,key) do
try do
:ets.lookup_element mod, key, 2
rescue
e in ArgumentError ->
raise EntryNotFoundError, type: mod, key: key
end
end
end #module DictEntry
defmodule Vendor do
use DictEntry
defstruct id: nil, name: nil, format: {1,1}, file: "(unknown)", line: 0
def index_for(val) do
[ id: val.id,
name: val.name]
end
def by_name(nil) do
%Vendor{}
end
def by_name(name) do
lookup! name: name
end
def by_id(id) do
lookup! id: id
end
end #module Vendor
defmodule Attribute do
use DictEntry
defstruct id: nil, name: nil, type: nil, opts: [], vendor: nil, file: "(unknown)", line: 0
def on_insert(val) do
v = Vendor.by_name val.vendor
%{val|vendor: v}
end
def index_for(val) do
[ id: {val.vendor.id, val.id},
name: val.name]
end
def by_name(name) do
lookup! name: name
end
def by_id(vendor \\ nil, id) do
lookup! id: {vendor,id}
end
end #module Attribute
defmodule Value do
use DictEntry
defstruct name: nil, attr: nil, value: nil, file: "(unknown)", line: 0
def on_insert(val) do
a = Attribute.lookup! name: val.attr
%{val|attr: a}
end
def index_for(val) do
[value: {val.attr.vendor.id, val.attr.id, val.value},
name: {val.attr.vendor.name, val.attr.name, val.name}]
end
def by_name(vendor \\ nil, attr, name) do
lookup! name: {vendor,attr,name}
end
def by_value(vendor \\ nil, attr, name) do
lookup! value: {vendor,attr,name}
end
end #module Value
defp init_ets() do
Vendor.init!
Attribute.init!
Value.init!
:ok
end
defmodule ParserError do
defexception [ file: "<unknown>", line: -1, msg: nil ]
def exception({:error,{line,_,msg}}) do
%ParserError{line: line, msg: msg}
end
def exception({:error,{line,_,msg},_}) do
%ParserError{line: line, msg: msg}
end
def message(e) do
"ParserError: at file: #{e.file}:#{e.line} #{e.msg}"
end
end
defp load(path) when is_binary(path) do
ctx = %{path: [path], vendor: nil, vendors: [], attrs: [], values: []}
ctx = load ctx
Enum.each ctx.vendors, &Vendor.insert/1
Enum.each ctx.attrs, &Attribute.insert/1
Enum.each ctx.values, &Value.insert/1
ctx
end
defp load(ctx) when is_map(ctx) do
path = hd ctx.path
#Logger.debug "Loading dict: #{path}"
try do
File.read!(path)
|> String.to_char_list
|> tokenlize!
|> parse!
|> (&process_dict ctx,&1).()
rescue
e in ParserError -> reraise %{e|file: path}, System.stacktrace
e -> reraise e,System.stacktrace
after
Logger.flush
end
end
defp tokenlize!(string) do
case :radius_dict_lex.string string do
{:ok,tokens, _} -> tokens
e -> raise ParserError, e
end
end
defp parse!(tokens) do
case :radius_dict_parser.parse(tokens) do
{:ok,spec} -> spec
e ->
e = ParserError.exception e
Logger.debug inspect _token_near(tokens,[],e.line-2,e.line+2)
raise e
end
end
defp _token_near(acc,[],_,_) do
:lists.reverse acc
end
defp _token_near(acc,[h|t],min,max) when :erlang.element(2,h) in min..max do
_token_near [h|acc],t,max,min
end
defp _token_near(acc,[_|t],min,max) do
_token_near acc,t,max,min
end
defp process_dict(ctx,[]) do
ctx
end
defp process_dict(ctx,[{:include,name}|tail]) do
target = ctx.path
|> Path.dirname
|> Path.join name
ctx = %{ctx| path: [target|ctx.path]}
ctx = load ctx
ctx = %{ctx| path: Enum.drop(ctx.path,1)}
process_dict ctx,tail
end
defp process_dict(ctx,[{:vendor_begin,name,_line}|tail]) do
name = to_string name
ctx = %{ctx| vendor: name}
process_dict ctx,tail
end
defp process_dict(ctx,[{:vendor_end,name,_line}|tail]) do
name = to_string(name)
if ctx.vendor == name do
ctx = %{ctx| vendor: nil }
process_dict ctx,tail
else
raise {:error,:end_vendor_not_match, name}
end
end
defp process_dict(ctx,[{:vendor,name,id,format,line}|tail]) do
name = to_string name
v = %Vendor{name: name, id: id, format: format, file: hd(ctx.path), line: line}
ctx = %{ctx| vendors: [v|ctx.vendors]}
process_dict ctx,tail
end
defp process_dict(ctx,[{:attribute,name,id,type,opts,line}|tail]) do
name = to_string(name)
a = %Attribute{name: name, id: id, type: type, opts: opts, vendor: ctx.vendor, file: hd(ctx.path), line: line}
ctx = %{ctx| attrs: [a|ctx.attrs]}
process_dict ctx,tail
end
defp process_dict(ctx,[{:value,attr,desc,id,line}|tail]) do
attr = to_string(attr)
desc = to_string(desc)
v = %Value{name: desc, attr: attr, value: id, file: hd(ctx.path), line: line}
ctx = %{ctx| values: [v|ctx.values]}
process_dict ctx,tail
end
defp process_dict(ctx,[head|tail]) do
Logger.warn "Unknown command: #{inspect head}"
process_dict ctx,tail
end
end