defmodule Cldr.Print do @moduledoc """ Implements `printf/3`, `sprintf/3` and `lprintf/3` in a manner largely compatible with the standard `C` language implementations. """ alias Cldr.Print.Parser import Cldr.Print.Splice @doc """ Formats and prints its arguments under control of a format. The format is a character string which contains two types of objects: plain characters, which are simply copied to standard output and format specifications, each of which causes printing of the next successive argument. ## Arguments * `format` is a format string. Information on the definition of a format string is below. * `args` is a list of arguments that are formatted according to the directives in the format string. The number of `args` in the list must be at least equal to the number of format specifiers in the format string. * `options` is a keyword list defining how the number is to be formatted. The valid options are: ## Options * `backend` is any `Cldr` backend. That is, any module that contains `use Cldr`. The default is the included `Cldr.Print.Backend` which is configured with only the locale `en`. * `:rounding_mode`: determines how a number is rounded to meet the precision of the format requested. The available rounding modes are `:down`, :half_up, :half_even, :ceiling, :floor, :half_down, :up. The default is `:half_even`. * `:number_system`: determines which of the number systems for a locale should be used to define the separators and digits for the formatted number. If `number_system` is an `atom` then `number_system` is interpreted as a number system. See `Cldr.Number.System.number_systems_for/2`. If the `:number_system` is `binary` then it is interpreted as a number system name. See `Cldr.Number.System.number_system_names_for/2`. The default is `:default`. * `:locale`: determines the locale in which the number is formatted. See `Cldr.known_locale_names/0`. The default is`Cldr.get_locale/0` which is the locale currently in affect for this `Process` and which is set by `Cldr.put_locale/1`. * `:device` which is used to define the output device for `printf/3`. The default is `:stdout`. ## Returns * `:ok` on success * `{:error, {exception, reason}}` if an error is detected ## Format definition Each format specification is introduced by the percent character (`%`). The remainder of the format specification includes, in the following order: * Optional format flags * Optional field width * Optional precision * Required format type The can be represented as: ``` %[flags][width][.precision]format_type ``` ## Format flags Zero or more of the following flags: | Flag | Description | | ----- | -------------------------------------------------------------------------------| | # | A `#` character specifying that the value should be printed in an alternate form. For `b`, `c`, `d`, `s` and `u` formats, this option has no effect. For the `o` formats the precision of the number is increased to force the first character of the output string to a zero. For the `x` (`X`) format, a non-zero result has the string `0x` (`0X`) prepended to it. For `a`, `A`, `e`, `E`, `f`, `F`, `g` and `G` formats, the result will always contain a decimal point, even if no digits follow the point (normally, a decimal point only appears in the results of those formats if a digit follows the decimal point). For `g` and `G` formats, trailing zeros are not removed from the result as they would otherwise be. | | - | A minus sign `-' which specifies left adjustment of the output in the indicated field. | | + | A `+` character specifying that there should always be a sign placed before the number when using signed formats. | | space | A space character specifying that a blank should be left before a positive number for a signed format. A `+` overrides a space if both are used. | | 0 | A zero `0` character indicating that zero-padding should be used rather than blank-padding. A `-` overrides a `0` if both are used. | | ' | Formats a number with digit grouping applied. The group size and grouping character are determined based upon the current processes locale or the `:locale` option to `printf/3` if provided. | | I | Formats a number using the native number system digits of the current processes locale or the `:locale` option to `printf/3` if provided. The option `:number_system` if provided takes precedence over this flag. | ## Field Width An optional digit string specifying a field width; if the output string has fewer bytes than the field width it will be blank-padded on the left (or right, if the left-adjustment indicator has been given) to make up the field width (note that a leading zero is a flag, but an embedded zero is part of a field width). ## Precision An optional period, `.`, followed by an optional digit string giving a precision which specifies the number of digits to appear after the decimal point, for `e` and `f` formats, or the maximum number of graphemes to be printed from a string. If the digit string is missing, the precision is treated as zero. ## Format Type A character which indicates the type of format to use (one of `diouxXfFeEgGaAs`). The uppercase formats differ from their lowercase counterparts only in that the output of the former is entirely in uppercase. | Format | Description | | ------ | -------------------------------------------------------------------------------| | diouXx | The argument is printed as a signed decimal (d or i), unsigned octal, unsigned decimal, or unsigned hexadecimal (X or x), respectively. | | fF | The argument is printed in the style `[-]ddd.ddd` where the number of d's after the decimal point is equal to the precision specification for the argument. If the precision is missing, 6 digits are given; if the precision is explicitly 0, no digits and no decimal point are printed. The values infinity and NaN are printed as `inf' and `nan', respectively. | | eE | The argument is printed in the style e `[-d.ddd+-dd]` where there is one digit before the decimal point and the number after is equal to the precision specification for the argument; when the precision is missing, 6 digits are produced. The values infinity and NaN are printed as `inf` and `nan`, respectively. | | gG | The argument is printed in style f or e (or in style E for a G format code), with the precision specifying the number of significant digits. The style used depends on the value converted: style e will be used only if the exponent resulting from the conversion is less than -4 or greater than the precision. Trailing zeroes are removed from the result; a decimal point appears only if it is followed by a digit. | | aA | The argument is printed in style `[-h.hhh+-pd]` where there is one digit before the hexadecimal point and the number after is equal to the precision specification for the argument; when the precision is missing, enough digits are produced to convey the argument's exact double-precision floating-point representation. The values infinity and NaN are printed as `inf` and `nan`, respectively. | | s | Graphemes from the string argument are printed until the end is reached or until the number of graphemes indicated by the precision specification is reached; however if the precision is 0 or missing, the string is printed entirely. | | % | Print a `%`; no argument is used. | ## Notes * The grouping separator, decimal point and exponent characters are defined in the current processes locale or as specified in the `:locale` option to `printf/3`. * In no case does a non-existent or small field width cause truncation of a field; padding takes place only if the specified field width exceeds the actual width. * `printf/3` calls `IO.write/2` and therefore there are no control characters emitted unless provided in the format string. This is consistent with the `C` implementation but different from `IO.puts/2`. """ def printf(format, args, options \\ []) do {device, options} = Keyword.pop(options, :device, :stdio) with {:ok, io_list} <- lprintf(format, args, options) do IO.write(device, io_list) end end @doc """ Returns a `{:ok, string}` after applying a format to a list of arguments. The arguments and options are the same as those for `printf/3` """ def sprintf(format, args, options \\ []) do with {:ok, io_list} <- lprintf(format, args, options) do {:ok, IO.iodata_to_binary(io_list)} end end @doc """ Returns a `string` or raises after applying a format to a list of arguments. The arguments and options are the same as those for `printf/3` """ def sprintf!(format, args, options \\ []) do case sprintf(format, args, options) do {:ok, string} -> string {:error, {exception, reason}} -> raise exception, reason end end @doc """ Returns an `{:ok, io_list}` after applying a format to a list of arguments. The arguments and options are the same as those for `printf/3` """ def lprintf(format, args, options \\ []) def lprintf(format, args, options) when is_list(args) do with {:ok, tokens} <- Parser.parse(format), {:ok, io_list} <- splice_arguments(tokens, args, options, &format/2) do {:ok, Enum.reverse(io_list)} end end def lprintf(format, arg, options) do lprintf(format, [arg], options) end @doc """ Returns an `io_list` or raises after applying a format to a list of arguments. The arguments and options are the same as those for `printf/3` """ def lprintf!(format, args, options \\ []) do case lprintf(format, args, options) do {:ok, string} -> string {:error, {exception, reason}} -> raise exception, reason end end @doc false defmacro mprintf(format, args, options \\ []) do args = if is_list(args), do: args, else: [args] with {:ok, tokens} <- Parser.parse(format), {:ok, io_list} <- splice_arguments(tokens, args, options, &identity/2) do quote do IO.write Keyword.get(unquote(options), :device, :stdout), IO.iodata_to_binary(format_list(unquote(Enum.reverse(io_list)))) end end end @doc false defmacro msprintf(format, args, options \\ []) do args = if is_list(args), do: args, else: [args] with {:ok, tokens} <- Parser.parse(format), {:ok, io_list} <- splice_arguments(tokens, args, options, &identity/2) do quote do {:ok, format_list(unquote(Enum.reverse(io_list))) |> IO.iodata_to_binary} end end end @doc false defmacro msprintf!(format, args, options \\ []) do args = if is_list(args), do: args, else: [args] with {:ok, tokens} <- Parser.parse(format), {:ok, io_list} <- splice_arguments(tokens, args, options, &identity/2) do quote do format_list(unquote(Enum.reverse(io_list))) |> IO.iodata_to_binary end end end @doc false defmacro mlprintf(format, args, options \\ []) do args = if is_list(args), do: args, else: [args] with {:ok, tokens} <- Parser.parse(format), {:ok, io_list} <- splice_arguments(tokens, args, options, &identity/2) do quote do {:ok, format_list(unquote(Enum.reverse(io_list)))} end end end @doc false defmacro mlprintf!(format, args, options \\ []) do args = if is_list(args), do: args, else: [args] with {:ok, tokens} <- Parser.parse(format), {:ok, io_list} <- splice_arguments(tokens, args, options, &identity/2) do quote do format_list(unquote(Enum.reverse(io_list))) end end end end