Definición de un campo del layout: dónde está, cuánto mide y cómo se lee.
Un campo es data. Se define con una keyword list y se valida al construirlo, antes de tocar un solo archivo.
iex> alias AnchoFijo.Campo
iex> campo = Campo.nuevo!(nombre: :monto, posicion: 11, largo: 12, tipo: :decimal, precision: 2)
iex> Campo.rango(campo)
{11, 22}Opciones
:nombre— átomo, obligatorio. Es la clave del registro parseado.:largo— entero positivo, obligatorio.:posicion— entero positivo, 1-based. Si se omite,AnchoFijo.Layoutla calcula encadenando el campo anterior.:tipo—:texto(default),:entero,:decimalo:fecha.:opcional— si estrue, un campo en blanco (o en ceros, para fechas) se lee comonilen vez de producir un diagnóstico. Defaultfalse.:trim—:ambos(default),:izquierda,:derechaofalse. Solo aplica a:texto; los tipos numéricos y de fecha siempre recortan espacios porque el relleno no es parte del dato.:relleno— carácter de relleno a recortar en:texto. Default" ".:precision— obligatorio en:decimal. Cantidad de decimales que el formato declara.:separador— en:decimal::implicito(default),:puntoo:coma.:formato— obligatorio en:fecha::aaaammddo:ddmmaaaa.
Tipos y valores devueltos
| tipo | valor |
|---|---|
:texto | String.t() ya transcodificado a UTF-8 |
:entero | integer() |
:decimal | {unidades, precision}, p. ej. {123456, 2} para 1234.56 |
:fecha | Date.t() |
Un :decimal nunca se convierte a float. Se devuelve como par
{unidades_minimas, precision} —centavos y escala— porque un monto que pasa
por punto flotante deja de cuadrar con la contabilidad del banco, y una
librería de lectura no debería obligar a depender de Decimal para evitarlo.
Summary
Types
Contexto de lectura que aporta el layout: cómo medir, cómo decodificar y en qué línea vamos, para que el diagnóstico sepa ubicarse.
Functions
Extrae y convierte el valor del campo desde una línea completa.
Última posición que ocupa el campo.
Nombre de la unidad de medida, para redactar diagnósticos.
Valida y construye un campo.
Igual que nuevo/1 pero levanta AnchoFijo.Error si la definición es inválida.
Rango de posiciones que ocupa el campo, 1-based e inclusivo en ambos extremos.
Types
@type contexto() :: %{ optional(:unidad) => :bytes | :caracteres, optional(:encoding) => :utf8 | :latin1, optional(:linea) => pos_integer() | nil }
Contexto de lectura que aporta el layout: cómo medir, cómo decodificar y en qué línea vamos, para que el diagnóstico sepa ubicarse.
@type t() :: %AnchoFijo.Campo{ formato: :aaaammdd | :ddmmaaaa | nil, largo: pos_integer(), nombre: atom(), opcional: boolean(), posicion: pos_integer() | nil, precision: non_neg_integer() | nil, relleno: String.t(), separador: :implicito | :punto | :coma, tipo: tipo(), trim: :ambos | :izquierda | :derecha | false }
@type tipo() :: :texto | :entero | :decimal | :fecha
@type valor() :: String.t() | integer() | {integer(), non_neg_integer()} | Date.t() | nil
Functions
@spec extraer(t(), binary(), contexto() | keyword()) :: {:ok, valor()} | {:error, AnchoFijo.Diagnostico.t()}
Extrae y convierte el valor del campo desde una línea completa.
iex> alias AnchoFijo.Campo
iex> campo = Campo.nuevo!(nombre: :monto, posicion: 1, largo: 8, tipo: :decimal, precision: 2)
iex> Campo.extraer(campo, "00123456")
{:ok, {123456, 2}}
iex> alias AnchoFijo.Campo
iex> campo = Campo.nuevo!(nombre: :nombre, posicion: 1, largo: 6)
iex> Campo.extraer(campo, <<74, 79, 83, 201, 32, 32>>, encoding: :latin1)
{:ok, "JOSÉ"}
iex> alias AnchoFijo.Campo
iex> campo = Campo.nuevo!(nombre: :fecha, posicion: 1, largo: 8, tipo: :fecha, formato: :aaaammdd)
iex> {:error, diagnostico} = Campo.extraer(campo, "20240230", linea: 4)
iex> AnchoFijo.Diagnostico.mensaje(diagnostico)
"línea 4, campo :fecha (posiciones 1-8): se esperaban una fecha válida en formato AAAAMMDD, llegaron \"20240230\"; el día no existe en ese mes"
@spec fin(t()) :: pos_integer()
Última posición que ocupa el campo.
iex> AnchoFijo.Campo.fin(%AnchoFijo.Campo{nombre: :a, posicion: 11, largo: 5})
15
@spec nombre_unidad(:bytes | :caracteres) :: String.t()
Nombre de la unidad de medida, para redactar diagnósticos.
iex> AnchoFijo.Campo.nombre_unidad(:bytes)
"bytes"
@spec nuevo(Enumerable.t()) :: {:ok, t()} | {:error, [AnchoFijo.Diagnostico.t()]}
Valida y construye un campo.
Devuelve {:ok, campo} o {:error, diagnosticos} con un diagnóstico por
problema encontrado en la definición.
iex> AnchoFijo.Campo.nuevo(nombre: :rut, largo: 10)
{:ok, %AnchoFijo.Campo{nombre: :rut, largo: 10, tipo: :texto}}
iex> {:error, [diagnostico]} = AnchoFijo.Campo.nuevo(nombre: :monto, largo: 12, tipo: :decimal)
iex> AnchoFijo.Diagnostico.mensaje(diagnostico)
"layout, campo :monto: se esperaba :precision declarada para un campo :decimal; sin precisión no se sabe si 1234 son 12,34 o 1234,00"
@spec nuevo!(Enumerable.t()) :: t()
Igual que nuevo/1 pero levanta AnchoFijo.Error si la definición es inválida.
iex> AnchoFijo.Campo.nuevo!(nombre: :fecha, largo: 8, tipo: :fecha, formato: :ddmmaaaa).formato
:ddmmaaaa
@spec rango(t()) :: {pos_integer(), pos_integer()}
Rango de posiciones que ocupa el campo, 1-based e inclusivo en ambos extremos.
iex> AnchoFijo.Campo.rango(%AnchoFijo.Campo{nombre: :a, posicion: 1, largo: 10})
{1, 10}