class Csv

class Csv

lib/csv.tya:2

Csv provides the csv/Csv standard library API.

Source
# Csv provides the csv/Csv standard library API.
class Csv
  # Csv.SEPARATOR provides the default CSV field separator.
  # @type String
  SEPARATOR: ","

  # Csv.headers stores the header order used for dictionary rows.
  # @type Array<String>
  headers: []

  # Csv.options stores CSV parsing and stringifying options.
  # @type Dict
  # @option options.separator String field separator.
  # @option options.header Boolean whether the first row is a header.
  options: { separator: Self.SEPARATOR, header: false }

  # Csv.rows stores the CSV rows held by this object.
  # @type Array
  rows: []

  # Csv.initialize applies optional CSV options.
  # @param options Dict? CSV parsing and stringifying options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether the first row is a header.
  initialize: options = nil ->
    self.apply_options(options)

  # Csv.dump provides the csv/Csv standard library operation.
  # @param options Dict? CSV stringifying options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether to include dictionary headers.
  # @return String serialized CSV text.
  dump: options = nil ->
    self.stringify(options)

  # Csv.parse provides the csv/Csv standard library operation.
  # @param text String? CSV text to parse; nil returns current rows.
  # @param options Dict? CSV parsing options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether the first row is a header.
  # @return Array parsed rows.
  parse: text = nil, options = nil ->
    if text == nil
      return self.rows
    self.apply_options(options)
    rows = self.parse_rows(text)
    if not self.options["header"]
      self.rows = rows
      return rows
    if rows.len() == 0
      self.rows = []
      return []
    header = rows[0]
    self.headers = header
    out = []
    i = 1
    while i < rows.len()
      r = rows[i]
      d = {}
      j = 0
      while j < header.len()
        if j < r.len()
          d[header[j]] = r[j]
        else
          d[header[j]] = ""
        j = j + 1
      out.push(d)
      i = i + 1
    self.rows = out
    out

  # Csv.stringify provides the csv/Csv standard library operation.
  # @param options Dict? CSV stringifying options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether to include dictionary headers.
  # @return String serialized CSV text.
  stringify: options = nil ->
    self.apply_options(options)
    if self.rows.len() == 0
      return ""
    if self.rows[0].class == Array
      return self.dump_array_rows()
    if self.rows[0].class == Dict
      header = self.headers
      if header.len() == 0
        header = self.rows[0].keys()
        self.headers = header
      out = ""
      if self.options["header"]
        j = 0
        while j < header.len()
          if j > 0
            out = out + self.options["separator"]
          out = out + self.escape_field(header[j])
          j = j + 1
        out = out + "\n"
      i = 0
      while i < self.rows.len()
        r = self.rows[i]
        j = 0
        while j < header.len()
          if j > 0
            out = out + self.options["separator"]
          v = ""
          if r.has(header[j])
            raw = r[header[j]]
            if raw.class != String
              raise error("csv.dump: row values must be strings")
            v = raw
          out = out + self.escape_field(v)
          j = j + 1
        out = out + "\n"
        i = i + 1
      return out
    raise error("csv.dump: rows must be array of arrays or array of dicts")

  private apply_options: options ->
    if options == nil
      return nil
    self.options.update(options)

  private dump_array_rows: ->
    out = ""
    i = 0
    while i < self.rows.len()
      r = self.rows[i]
      j = 0
      while j < r.len()
        if j > 0
          out = out + self.options["separator"]
        if r[j].class != String
          raise error("csv.dump: row values must be strings")
        out = out + self.escape_field(r[j])
        j = j + 1
      out = out + "\n"
      i = i + 1
    out

  private escape_field: field ->
    separator = self.options["separator"]
    needs_quote = field.sequence().any?(
      c -> c == separator or c == "\"" or c == "\n" or c == chr(13)
    )
    if not needs_quote
      return field
    "\"" + field.replace("\"", "\"\"") + "\""

  private parse_rows: text ->
    rows = []
    n = text.byte_len()
    i = 0
    cr = chr(13)
    while i < n
      row = []
      field = ""
      in_quote = false
      ended = false
      while i < n and not ended
        c = text[i]
        if in_quote
          if c == "\""
            if i + 1 < n and text[i + 1] == "\""
              field = field + "\""
              i = i + 2
            else
              in_quote = false
              i = i + 1
          else
            field = field + c
            i = i + 1
        elseif c == "\""
          in_quote = true
          i = i + 1
        else
          if c == self.options["separator"]
            row.push(field)
            field = ""
            i = i + 1
          elseif c == "\n"
            row.push(field)
            i = i + 1
            ended = true
          else
            if c == cr
              row.push(field)
              i = i + 1
              if i < n and text[i] == "\n"
                i = i + 1
              ended = true
            else
              field = field + c
              i = i + 1
      if in_quote
        raise error("csv.parse: unterminated quoted field")
      if not ended
        row.push(field)
      rows.push(row)
    rows

Class Constants

SEPARATOR

Csv.SEPARATOR

lib/csv.tya:5

Csv.SEPARATOR provides the default CSV field separator.

Source
  # Csv.SEPARATOR provides the default CSV field separator.
  # @type String
  SEPARATOR: ","

Instance Variables

headers

Csv.headers

lib/csv.tya:9

Csv.headers stores the header order used for dictionary rows.

Source
  # Csv.headers stores the header order used for dictionary rows.
  # @type Array<String>
  headers: []

options

Csv.options

lib/csv.tya:15

Csv.options stores CSV parsing and stringifying options.

Source
  # Csv.options stores CSV parsing and stringifying options.
  # @type Dict
  # @option options.separator String field separator.
  # @option options.header Boolean whether the first row is a header.
  options: { separator: Self.SEPARATOR, header: false }

rows

Csv.rows

lib/csv.tya:19

Csv.rows stores the CSV rows held by this object.

Source
  # Csv.rows stores the CSV rows held by this object.
  # @type Array
  rows: []

Methods

dump

Csv.dump(options = nil)

lib/csv.tya:33

Csv.dump provides the csv/Csv standard library operation.

Source
  # Csv.dump provides the csv/Csv standard library operation.
  # @param options Dict? CSV stringifying options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether to include dictionary headers.
  # @return String serialized CSV text.
  dump: options = nil ->
    self.stringify(options)

initialize

Csv.initialize(options = nil)

lib/csv.tya:25

Csv.initialize applies optional CSV options.

Source
  # Csv.initialize applies optional CSV options.
  # @param options Dict? CSV parsing and stringifying options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether the first row is a header.
  initialize: options = nil ->
    self.apply_options(options)

parse

Csv.parse(text = nil, options = nil)

lib/csv.tya:42

Csv.parse provides the csv/Csv standard library operation.

Source
  # Csv.parse provides the csv/Csv standard library operation.
  # @param text String? CSV text to parse; nil returns current rows.
  # @param options Dict? CSV parsing options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether the first row is a header.
  # @return Array parsed rows.
  parse: text = nil, options = nil ->
    if text == nil
      return self.rows
    self.apply_options(options)
    rows = self.parse_rows(text)
    if not self.options["header"]
      self.rows = rows
      return rows
    if rows.len() == 0
      self.rows = []
      return []
    header = rows[0]
    self.headers = header
    out = []
    i = 1
    while i < rows.len()
      r = rows[i]
      d = {}
      j = 0
      while j < header.len()
        if j < r.len()
          d[header[j]] = r[j]
        else
          d[header[j]] = ""
        j = j + 1
      out.push(d)
      i = i + 1
    self.rows = out
    out

stringify

Csv.stringify(options = nil)

lib/csv.tya:77

Csv.stringify provides the csv/Csv standard library operation.

Source
  # Csv.stringify provides the csv/Csv standard library operation.
  # @param options Dict? CSV stringifying options.
  # @option options.separator String field separator.
  # @option options.header Boolean whether to include dictionary headers.
  # @return String serialized CSV text.
  stringify: options = nil ->
    self.apply_options(options)
    if self.rows.len() == 0
      return ""
    if self.rows[0].class == Array
      return self.dump_array_rows()
    if self.rows[0].class == Dict
      header = self.headers
      if header.len() == 0
        header = self.rows[0].keys()
        self.headers = header
      out = ""
      if self.options["header"]
        j = 0
        while j < header.len()
          if j > 0
            out = out + self.options["separator"]
          out = out + self.escape_field(header[j])
          j = j + 1
        out = out + "\n"
      i = 0
      while i < self.rows.len()
        r = self.rows[i]
        j = 0
        while j < header.len()
          if j > 0
            out = out + self.options["separator"]
          v = ""
          if r.has(header[j])
            raw = r[header[j]]
            if raw.class != String
              raise error("csv.dump: row values must be strings")
            v = raw
          out = out + self.escape_field(v)
          j = j + 1
        out = out + "\n"
        i = i + 1
      return out
    raise error("csv.dump: rows must be array of arrays or array of dicts")