AnchoFijo.Detector (ancho_fijo v0.1.0)

Copy Markdown View Source

Interroga el archivo antes de parsearlo.

Este es el módulo que justifica la librería. La especificación que manda el cliente y el archivo que manda el banco son dos documentos distintos, y la diferencia se descubre siempre tarde: después de escribir el parser, en la primera corrida con datos reales, un viernes. detectar/2 mueve ese descubrimiento al día uno.

iex> {:ok, reporte} = AnchoFijo.Detector.detectar("12345678-9JUAN PEREZ  \n98765432-1ANA SOTO    \n")
iex> {reporte.largo_consistente?, reporte.largo_predominante, reporte.terminador}
{true, 22, :lf}
iex> reporte.probablemente_ancho_fijo?
true

Y el caso que más veces salva el proyecto: el archivo que dijeron que era de ancho fijo y no lo es.

iex> {:ok, reporte} = AnchoFijo.Detector.detectar("juan;100\nmaria;20000\n")
iex> reporte.probablemente_ancho_fijo?
false
iex> reporte.delimitador_sugerido
";"

El reporte

Un mapa con estas claves:

  • :lineas_analizadas — líneas con contenido consideradas.
  • :lineas_vacias — líneas en blanco encontradas y excluidas del análisis.
  • :muestra_truncada? — si se leyó solo parte del archivo.
  • :largos — frecuencia de cada largo de línea en bytes, %{120 => 18}.
  • :largo_consistente?true si hay un solo largo.
  • :largo_predominante — el largo más frecuente, o nil si no hay líneas.
  • :largos_en_caracteres — igual que :largos pero en caracteres, solo si el archivo es UTF-8 con multibyte; nil en otro caso.
  • :encoding_probable:ascii, :utf8 o :latin1.
  • :bytes_no_utf8 — hasta cinco bytes que rompen UTF-8, con línea y posición.
  • :terminador:crlf, :lf, :cr, :mixto o :ninguno.
  • :termina_con_terminador?nil si la muestra está truncada.
  • :delimitadores — análisis de ;, ,, tab y |.
  • :delimitador_sugerido — el delimitador que sugiere que el archivo es delimitado, o nil.
  • :probablemente_ancho_fijo? — el veredicto, resumido.
  • :observaciones — el reporte en prosa, para pegar en un correo.

Opciones

  • :lineas — máximo de líneas a analizar. Default 200.
  • :bytes — máximo de bytes a leer del archivo. Default 64.000.
  • :desde:auto (default), :archivo o :contenido, para desambiguar si el binario es una ruta o el contenido mismo.
  • :layout — un AnchoFijo.Layout o una definición, para contrastar lo que el archivo dice con lo que la especificación declara.
  • :delimitadores — lista de candidatos a delimitador. Default [";", ",", "\t", "|"].

Summary

Functions

Contrasta un reporte con un layout y devuelve los desacuerdos como diagnósticos.

Analiza una muestra y devuelve el reporte.

Types

reporte()

@type reporte() :: %{
  lineas_analizadas: non_neg_integer(),
  lineas_vacias: non_neg_integer(),
  muestra_truncada?: boolean(),
  largos: %{optional(non_neg_integer()) => pos_integer()},
  largo_consistente?: boolean(),
  largo_predominante: non_neg_integer() | nil,
  largos_en_caracteres: %{optional(non_neg_integer()) => pos_integer()} | nil,
  encoding_probable: :ascii | :utf8 | :latin1,
  bytes_no_utf8: [map()],
  terminador: :crlf | :lf | :cr | :mixto | :ninguno,
  termina_con_terminador?: boolean() | nil,
  delimitadores: %{optional(String.t()) => map()},
  delimitador_sugerido: String.t() | nil,
  probablemente_ancho_fijo?: boolean(),
  observaciones: [String.t()]
}

Functions

contrastar(reporte, layout)

@spec contrastar(reporte(), AnchoFijo.Layout.t() | Enumerable.t()) :: [
  AnchoFijo.Diagnostico.t()
]

Contrasta un reporte con un layout y devuelve los desacuerdos como diagnósticos.

Es la pregunta "¿esto es realmente lo que me dijeron que es?" en forma de función. Una lista vacía significa que el archivo y la especificación concuerdan en lo que se puede verificar sin parsear.

iex> {:ok, reporte} = AnchoFijo.Detector.detectar("ABCDEFGH\nIJKLMNOP\n")
iex> layout = AnchoFijo.Layout.nuevo!(campos: [[nombre: :a, largo: 10]])
iex> [diagnostico] = AnchoFijo.Detector.contrastar(reporte, layout)
iex> AnchoFijo.Diagnostico.mensaje(diagnostico)
"entrada: se esperaba un largo de línea de 10 bytes según el layout, llegó 8; el archivo tiene 2 bytes menos por línea: falta un campo, o el layout es de otra versión del formato"

iex> {:ok, reporte} = AnchoFijo.Detector.detectar("ABCDEFGH\nIJKLMNOP\n")
iex> AnchoFijo.Detector.contrastar(reporte, AnchoFijo.Layout.nuevo!(campos: [[nombre: :a, largo: 8]]))
[]

detectar(entrada, opts \\ [])

@spec detectar(
  term(),
  keyword()
) :: {:ok, reporte()} | {:error, [AnchoFijo.Diagnostico.t()]}

Analiza una muestra y devuelve el reporte.

Acepta el contenido como binario, una ruta a un archivo o —vía :desde— la desambiguación explícita entre ambos.

iex> {:ok, reporte} = AnchoFijo.Detector.detectar("AAAA\nBBB\n")
iex> reporte.largos
%{3 => 1, 4 => 1}
iex> reporte.largo_consistente?
false

Un archivo en latin-1 se delata solo:

iex> {:ok, reporte} = AnchoFijo.Detector.detectar(<<74, 79, 83, 201, 10>>)
iex> reporte.encoding_probable
:latin1
iex> hd(reporte.bytes_no_utf8)
%{linea: 1, posicion: 4, byte: 201}