iyi

Standard library, 0.18.0

std/mime

Media types for naming and serving files: the type a file name's extension stands for, the extensions a type is written with, whether a type is worth compressing on the wire, the type with its charset, and RFC 9110's type/subtype; name=value parsed.

import std/mime to use it. Source: src/std/mime.iyi at v0.18.0.

The table is fixed and lives here. Crystal's MIME and Python's mimetypes read /etc/mime.types and its relatives, so the same name answers differently on two machines; this one reads no file and answers the same everywhere. The values are IANA's registrations where one exists and what browsers and Go's mime send where none does: .js and .mjs are text/javascript (RFC 9239), .xml is application/xml (RFC 7303 prefers it to text/xml), .ico is IANA's image/vnd.microsoft.icon rather than the older image/x-icon, which browsers accept as well. Every entry is checked against Python's table by bench/std_mime_exercise.sh, which lists the few deliberate differences and why.

Left out: registering types at run time, which would make the answer depend on what ran before, and sniffing a type from the bytes of a file. Extensions are looked up by the last dot of the base name only, so a.tar.gz is application/gzip, as every server answers it.

MIMEstruct

std/mimeiyi doc src/std/mime.iyi
pub struct MIME
  # Whether a body of this type shrinks under gzip or deflate: text,
  # JSON, JavaScript, XML and their `+json`/`+xml` kin (SVG among them),
  # YAML, TOML, scripts, WebAssembly, uncompressed fonts and archives,
  # bitmaps and icons. Not images, audio and video that are already
  # compressed, nor zip, gzip and the other archives, nor WOFF and WOFF2,
  # which are compressed fonts. WebAssembly is included because its code
  # roughly halves under gzip, which is why servers compress it.
  def self.compressible?(media_type : String) : Bool
  # The extensions a media type is written with, dotted and sorted
  # ("text/html" is [".htm", ".html"]). Parameters are ignored and case
  # does not matter; empty when the type is unknown. The array is a fresh
  # copy the caller may change.
  def self.extensions(media_type : String) : Array(String)
  # The same, falling back to *default*.
  def self.from_extension(name : String, default : String = "application/octet-stream") : String
  # The media type for a file name or extension: ".html", "html",
  # "index.HTML" and "a/b.tar.gz" (by its last extension) all work, in
  # any case. A path whose base name has no dot has no extension; a bare
  # word is taken as the extension itself. Nil when unknown.
  def self.from_extension?(name : String) : String | ::Nil
  def initialize
  # RFC 9110 §8.3.1: `type/subtype` followed by `; name=value` parameters,
  # whitespace allowed around the semicolons and at either end, a value a
  # token or a quoted string whose backslash escapes are undone. Answers
  # the type lowercased and the parameters with lowercased names and
  # values as written; a name given twice keeps its last value. Nil when
  # any part is not a token, a parameter has no `=`, or a quote is left
  # open.
  def self.parse?(value : String) : ::Tuple(String, Hash(String, String)) | ::Nil
  # The media type with `; charset=utf-8` added when it is text, JSON,
  # JavaScript or XML (`+json` and `+xml` included) and names no charset
  # yet. Anything else, and anything that does not parse, comes back as
  # given.
  def self.with_charset(media_type : String) : String
end