# `AnchoFijo.Detector`
[🔗](https://github.com/galenzo17/ancho_fijo/blob/v0.1.0/lib/ancho_fijo/detector.ex#L1)

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", "|"]`.

# `reporte`

```elixir
@type reporte() :: %{
  lineas_analizadas: non_neg_integer(),
  lineas_vacias: non_neg_integer(),
  muestra_truncada?: boolean(),
  largos: %{optional(non_neg_integer()) =&gt; pos_integer()},
  largo_consistente?: boolean(),
  largo_predominante: non_neg_integer() | nil,
  largos_en_caracteres: %{optional(non_neg_integer()) =&gt; 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()) =&gt; map()},
  delimitador_sugerido: String.t() | nil,
  probablemente_ancho_fijo?: boolean(),
  observaciones: [String.t()]
}
```

# `contrastar`

```elixir
@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`

```elixir
@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}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
