Current section

Files

Jump to
posexional lib posexional file.ex
Raw

lib/posexional/file.ex

defmodule Posexional.File do
@moduledoc """
a Posexional.File is the main struct to manage a positional file
"""
alias Posexional.Row
alias Posexional.Field
defstruct \
rows: [],
separator: "\n"
def new(rows, separator \\ nil)
def new(rows, nil) do
%Posexional.File{rows: rows, separator: "\n"}
end
def new(rows, separator) do
%Posexional.File{rows: rows, separator: separator}
end
@doc """
creates a file from values
## Examples
iex> Posexional.File.write(
...> Posexional.File.new([ Posexional.Row.new(:row_test, [ Posexional.Field.Value.new(:test1, 5) ]) ]),
...> [row_test: [test1: "test"], row_test: [test1: "t"]]
...> )
"test \\nt "
iex> Posexional.File.write(
...> Posexional.File.new([ Posexional.Row.new(:row_test, [ Posexional.Field.Value.new(:test1, 5) ]) ]),
...> [row_test: [test1: "test"], ne: [test1: "t"]]
...> )
** (RuntimeError) row ne not found
"""
@spec write(%Posexional.File{}, Keyword.t) :: binary
def write(file = %Posexional.File{separator: separator}, values) do
file
|> manage_counters
|> get_lines(values)
|> Enum.join(separator)
end
@spec write_path!(%Posexional.File{}, Keyword.t, binary) :: binary
def write_path!(file = %Posexional.File{separator: separator}, values, path) do
File.open(path, [:write], fn (handle) ->
file
|> get_lines(values)
|> Enum.map(fn line ->
IO.write handle, line
IO.write handle, separator
end)
end)
end
@spec read(%Posexional.File{}, binary) :: Keyword.t
def read(%Posexional.File{separator: separator, rows: rows}, content) do
content
|> String.split(separator)
|> Enum.filter(fn
"" -> false
_ -> true
end)
|> Enum.flat_map(fn content ->
row = guess_row(content, rows)
if is_nil(row) do
[content]
else
Row.read(row, content)
end
end)
end
@spec get_lines(%Posexional.File{}, Keyword.t) :: []
defp get_lines(file, values) do
values
|> Stream.map(fn {row_name, values} -> {find_row(file, row_name), row_name, values} end)
|> Enum.map(fn {row, row_name, values} ->
if is_nil(row) do
raise "row #{row_name} not found"
end
{:ok, out} = Row.write(row, values)
out
end)
end
@doc """
adds a generator for every progressive_number_field in the file.
The fields are grouped by name, so that you can specify many counters for every row
"""
@spec manage_counters(%Posexional.File{}) :: %Posexional.File{}
def manage_counters(file = %Posexional.File{rows: rows}) do
counters = get_counters(file)
%{file | rows: Enum.map(rows, &(Row.manage_counters(&1, counters)))}
end
@spec get_counters(%Posexional.File{}) :: [{atom, pid}]
def get_counters(%Posexional.File{rows: rows}) do
rows
|> Enum.flat_map(&(&1.fields))
|> Enum.flat_map(fn
%Field.ProgressiveNumber{name: name} -> [name]
_ -> []
end)
|> Enum.uniq
|> Enum.map(fn name ->
{:ok, pid} = Agent.start_link(fn -> 1 end)
{name, pid}
end)
end
@spec guess_row(binary, [%Row{}]) :: %Row{}
defp guess_row(content, rows) do
Enum.find(rows, nil, fn
%Row{row_guesser: :always} -> true
%Row{row_guesser: :never} -> false
%Row{row_guesser: row_guesser} when is_function(row_guesser) -> row_guesser.(content)
end)
end
@spec find_row(%Posexional.File{}, atom) :: %Row{}
def find_row(%Posexional.File{rows: rows}, name) do
Enum.find(rows, nil, fn %Row{name: row_name} -> row_name === name end)
end
end