Delimited.Field (Delimited v0.4.0)

Copy Markdown View Source

One declared column.

Built by Delimited.Schema.field/3 at compile time. Read it back with MySchema.__delimited__(:fields) when you need to generate documentation, a template file, or a user-facing column list.

Options

  • :header - the column name in the file. Defaults to the field name. Set it whenever the file's spelling is not a valid atom, which is most real files: field :employee_id, :integer, header: "Employee ID".

  • :default - the term used when the cell is empty or the column is absent. Defaults to nil. The default is used as declared and is never cast, so it must already be a value of the field's type. Writing nil is refused when reading the resulting null cell would return a non-nil default.

  • :required - when true, an empty cell is a :required_field_missing error instead of nil. Defaults to false. A required field cannot also declare a default, because the default would make the requirement unreachable.

  • :trim - strip surrounding whitespace from the cell before reading it. Defaults to the dialect's :trim. The writer refuses text that this rule would change.

  • :null - the strings that mean "no value" for this field. Defaults to the dialect's :null. Use it for the file that writes "N/A" in one column and leaves the rest blank. The writer refuses a non-nil value whose text is one of these strings.

  • :format - how a date or time is written, where the file does not use ISO 8601: field :invoiced_on, :date, format: "%d/%m/%Y". Give a list to read more than one spelling. Only :date, :time, :naive_datetime, and :utc_datetime accept it. See Delimited.Type for the directives.

Any other option is passed to a custom type. Built-in types reject unknown options, so a misspelt :heder fails the build rather than reading the wrong column.

Fixed-width options

Under layout: :fixed a field is a byte range rather than a cell, and these three options decide which bytes and how they are padded. They are rejected under the delimited layout, where nothing could honour them.

  • :at - the field's position as a 1-based, inclusive range, as a file specification writes it: a field documented as "positions 8-15" is at: 8..15. Required under the fixed layout. Positions not covered by any field are filler, and are neither read nor written.

  • :align - which end of the field the value sits at: :left pads on the right, :right pads on the left. Defaults to :right for :integer, :float, and :decimal, and to :left for everything else, which is what specifications almost always mean. Declare it where yours does not.

  • :pad - the byte that fills the rest of the field, as a one-character string or a codepoint. Defaults to a space.

Reading a padded field

Pad bytes are stripped from the padded side, and the value then goes through :trim, :null, and its type as any other cell does. A space-padded field holding only spaces therefore has no value, because the empty string is the default null string.

A field padded with anything else has to distinguish two cases, and does:

  • "00000000" in a zero-padded numeric field reads as 0. The field keeps its last pad byte, because stripping it to nothing would turn a stated zero into a missing value and no later check would reveal it.
  • " " in that same field reads as nil. A file that fills a numeric field with digits when it has a number leaves it blank when it has none, so spaces where digits were expected mean absent rather than zero.

Writing a padded field

A value is padded with the field's :pad. A field holding nil is left blank, whatever its :pad, which is the counterpart of the rule above: an empty field filled with zeros would state a number the row never held. The writer refuses nil if the field is required or if reading a blank field would return a non-nil default.

A value wider than its field is a :value_too_wide error. Truncating it would produce a file that parses and lies.

A type must be able to write itself narrow enough. :boolean writes "true" and "false", so a one-character flag column wants {:enum, [true: "Y", false: "N"]} rather than :boolean.

Summary

Types

A field's position as a zero-based byte offset and a length.

t()

Functions

Returns the field's alignment, resolving the default from its type.

Returns the field's declared position as it was written, for a message.

Returns the position one past the field's last byte.

Returns the field's pad byte, which defaults to a space.

Types

position()

@type position() :: {non_neg_integer(), pos_integer()}

A field's position as a zero-based byte offset and a length.

t()

@type t() :: %Delimited.Field{
  align: :left | :right | nil,
  at: position() | nil,
  default: term(),
  header: String.t(),
  name: atom(),
  null: [String.t()] | nil,
  opts: keyword(),
  pad: byte() | nil,
  required: boolean(),
  trim: boolean() | nil,
  type: Delimited.Type.t()
}

Functions

alignment(field)

@spec alignment(t()) :: :left | :right

Returns the field's alignment, resolving the default from its type.

declared_at(field)

@spec declared_at(t()) :: Range.t()

Returns the field's declared position as it was written, for a message.

ends_at(field)

@spec ends_at(t()) :: non_neg_integer()

Returns the position one past the field's last byte.

padding(field)

@spec padding(t()) :: byte()

Returns the field's pad byte, which defaults to a space.