#!/usr/bin/env ruby
# frozen_string_literal: true

# toml-test decoder shim for toml-rb.
# Protocol: https://github.com/toml-lang/toml-test
#
# Reads TOML from stdin, parses with TomlRB, writes tagged JSON to stdout.
# On parse error, writes the error to stderr and exits 1.

require "bundler/setup"
require "toml-rb"
require "json"
require "date"

module TomlTestDecoder
  module_function

  def encode(value)
    case value
    when Hash
      value.each_with_object({}) { |(k, v), h| h[k.to_s] = encode(v) }
    when Array
      value.map { |v| encode(v) }
    when String
      {"type" => "string", "value" => value}
    when true, false
      {"type" => "bool", "value" => value.to_s}
    when Integer
      {"type" => "integer", "value" => value.to_s}
    when Float
      str =
        if value.nan?
          "nan"
        elsif value.infinite?
          value.positive? ? "inf" : "-inf"
        else
          value.to_s
        end
      {"type" => "float", "value" => str}
    when TomlRB::LocalDate
      {"type" => "date-local", "value" => value.strftime("%Y-%m-%d")}
    when TomlRB::LocalTime
      {"type" => "time-local", "value" => value.strftime("%H:%M:%S.%9N")}
    when TomlRB::LocalDateTime
      {"type" => "datetime-local", "value" => value.strftime("%Y-%m-%dT%H:%M:%S.%9N")}
    when Time
      str = value.strftime("%Y-%m-%dT%H:%M:%S")
      nsec = value.nsec
      str += "." + format("%09d", nsec).sub(/0+\z/, "") if nsec.positive?
      zone = value.utc? ? "Z" : value.strftime("%:z")
      str += zone
      {"type" => "datetime", "value" => str}
    when Date
      {"type" => "date-local", "value" => value.iso8601}
    else
      raise "toml-test-decoder: unhandled type #{value.class}: #{value.inspect}"
    end
  end
end

begin
  input = $stdin.read.force_encoding("UTF-8")
  parsed = TomlRB.parse(input)
  $stdout.write(JSON.generate(TomlTestDecoder.encode(parsed)))
  $stdout.write("\n")
rescue StandardError, SystemStackError => e
  warn("#{e.class}: #{e.message}")
  # toml-test treats exit 1 as rejection and other nonzero exits as failures.
  exit(e.is_a?(TomlRB::Error) ? 1 : 2)
end
