Custom formatting for Rust.

Overview

custom-format

version Minimum supported Rust version Documentation

This crate extends the standard formatting syntax with custom format specifiers, by providing custom formatting macros.

It uses : (a space and a colon) as a separator before the format specifier, which is not a syntax currently accepted and allows supporting standard specifiers in addition to custom specifiers. It also supports format args capture even on older versions of Rust, since it manually adds the named parameter if missing.

This library comes in two flavors, corresponding to the following features:

  • compile-time (enabled by default)

    The set of possible custom format specifiers is defined at compilation, so invalid specifiers can be checked at compile-time. This allows the library to have the same performance as when using the standard library formatting traits.

  • runtime (enabled by default)

    The formatting method dynamically checks the format specifier at runtime for each invocation. This is a slower version, but has a lower MSRV for greater compatibility.

Documentation

Documentation is hosted on docs.rs.

Example

Code
use custom_format as cfmt;

use core::fmt;

pub struct DateTime {
    year: i32,
    month: u8,
    month_day: u8,
    hour: u8,
    minute: u8,
    second: u8,
    nanoseconds: u32,
}

macro_rules! impl_custom_format_for_datetime {
    (match spec { $($spec:literal => $func:expr $(,)?)* }) => {
        use cfmt::compile_time::{spec, CustomFormat};
        $(
            impl CustomFormat<{ spec($spec) }> for DateTime {
                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                    ($func as fn(&Self, &mut fmt::Formatter) -> fmt::Result)(self, f)
                }
            }
        )*
    };
}

// Static format specifiers, checked at compile-time
impl_custom_format_for_datetime!(match spec {
    // Year with pad for at least 4 digits
    "%Y" => |this, f| write!(f, "{:04}", this.year),
    // Year % 100 (00..99)
    "%y" => |this, f| write!(f, "{:02}", (this.year % 100).abs()),
    // Month of the year, zero-padded (01..12)
    "%m" => |this, f| write!(f, "{:02}", this.month),
    // Day of the month, zero-padded (01..31)
    "%d" => |this, f| write!(f, "{:02}", this.month_day),
    // Hour of the day, 24-hour clock, zero-padded (00..23)
    "%H" => |this, f| write!(f, "{:02}", this.hour),
    // Minute of the hour (00..59)
    "%M" => |this, f| write!(f, "{:02}", this.minute),
    // Second of the minute (00..60)
    "%S" => |this, f| write!(f, "{:02}", this.second),
    // Date (%m/%d/%y)
    "%D" => {
        |this, f| {
            let month = cfmt::custom_formatter!("%m", this);
            let day = cfmt::custom_formatter!("%d", this);
            let year = cfmt::custom_formatter!("%y", this);
            write!(f, "{}/{}/{}", month, day, year)
        }
    }
});

// Dynamic format specifiers, checked at runtime
impl cfmt::runtime::CustomFormat for DateTime {
    fn fmt(&self, f: &mut fmt::Formatter, spec: &str) -> fmt::Result {
        let mut chars = spec.chars();
        match (chars.next(), chars.next_back()) {
            // Nanoseconds with n digits (%nN)
            (Some('%'), Some('N')) => match chars.as_str().parse() {
                Ok(n) if n > 0 => {
                    if n <= 9 {
                        write!(f, "{:0width$}", self.nanoseconds / 10u32.pow(9 - n as u32), width = n)
                    } else {
                        write!(f, "{:09}{:0width$}", self.nanoseconds, 0, width = n - 9)
                    }
                }
                _ => Err(fmt::Error),
            },
            _ => Err(fmt::Error),
        }
    }
}

let dt = DateTime {
    year: 1836,
    month: 5,
    month_day: 18,
    hour: 23,
    minute: 45,
    second: 54,
    nanoseconds: 123456789,
};

// Expands to:
//
// match (&("DateTime"), &dt) {
//     (arg0, arg1) => ::std::println!(
//         "The {0:?} is: {1}-{2}-{3} {4}:{5}:{6}.{7}",
//         arg0,
//         ::custom_format::custom_formatter!("%Y", arg1),
//         ::custom_format::custom_formatter!("%m", arg1),
//         ::custom_format::custom_formatter!("%d", arg1),
//         ::custom_format::custom_formatter!("%H", arg1),
//         ::custom_format::custom_formatter!("%M", arg1),
//         ::custom_format::custom_formatter!("%S", arg1),
//         ::custom_format::runtime::CustomFormatter::new("%6N", arg1)
//     ),
// }
//
// Output: `The "DateTime" is: 1836-05-18 23:45:54.123456`
//
// The custom format specifier is interpreted as a compile-time specifier by default,
// or as a runtime specifier if it is inside "<>".
cfmt::println!(
    "The {ty:?} is: {dt :%Y}-{dt :%m}-{dt :%d} {dt :%H}:{dt :%M}:{dt :%S}.{dt :<%6N>}",
    ty = "DateTime",
);

// Compile-time error since "%h" is not a valid format specifier
// cfmt::println!("{0 :%h}", dt);

// Panic at runtime since "%h" is not a valid format specifier
// cfmt::println!("{0 :<%h>}", dt);

Compiler support

Requires rustc 1.48+ for the runtime feature and rustc 1.51+ for the compile-time feature.

License

This project is licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

You might also like...
Rust-blog - Educational blog posts for Rust beginners

pretzelhammer's Rust blog 🦀 I write educational content for Rust beginners and Rust advanced beginners. My posts are listed below in reverse chronolo

The ray tracer challenge in rust - Repository to follow my development of
The ray tracer challenge in rust - Repository to follow my development of "The Raytracer Challenge" book by Jamis Buck in the language Rust

The Ray Tracer Challenge This repository contains all the code written, while step by implementing Ray Tracer, based on the book "The Ray Tracer Chall

Learn-rust-the-hard-way - "Learn C The Hard Way" by Zed Shaw Converted to Rust

Learn Rust The Hard Way This is an implementation of Zed Shaw's Learn X The Hard Way for the Rust Programming Language. Installing Rust TODO: Instruct

Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]
Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]

Rust Latam: procedural macros workshop This repo contains a selection of projects designed to learn to write Rust procedural macros — Rust code that g

The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language.

rcc The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language. Compilers Language Co

Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed.

integra8 Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed. | This repo is in a "work in progress"

Neofetch but in Rust (rust-toml-fetch)
Neofetch but in Rust (rust-toml-fetch)

rtfetch Configuration Recompile each time you change the config file logo = "arch.logo" # in src/assets. info = [ "", "", "yellow{host_n

Rust Sandbox [code for 15 concepts of Rust language]

Rust-Programming-Tutorial Rust Sandbox [code for 15 concepts of Rust language]. The first time I've been introduced to Rust was on January 2022, you m

TypeRust - simple Rust playground where you can build or run your Rust code and share it with others

Rust playground Welcome to TypeRust! This is a simple Rust playground where you can build or run your Rust code and share it with others. There are a

Comments
  • Update litrs requirement from 0.2.3 to 0.3.0

    Update litrs requirement from 0.2.3 to 0.3.0

    ⚠️ Dependabot is rebasing this PR ⚠️

    Rebasing might not happen immediately, so don't worry if this takes some time.

    Note: if you make any changes to this PR yourself, they will take precedence over the rebase.


    Updates the requirements on litrs to permit the latest version.

    Release notes

    Sourced from litrs's releases.

    v0.3.0

    Breaking

    • Bump MSRV (minimal supported Rust version) to 1.54

    Added

    • Add raw_input and into_raw_input to non-bool *Lit types
    • Add impl From<*Lit> for pm::Literal (for non-bool literals)
    • Add impl From<BoolLit> for pm::Ident

    Fixed

    • Fix link to reference and clarify bool literals (#7)

    Internals

    • Move lots of parsing code into non-generic functions (this hopefully reduces compile times)
    • To implement [into_]raw_input for integer and float literals, their internals were changed a bit so that they store the full input string now.
    Changelog

    Sourced from litrs's changelog.

    [0.3.0] - 2022-12-19

    Breaking

    • Bump MSRV (minimal supported Rust version) to 1.54

    Added

    • Add raw_input and into_raw_input to non-bool *Lit types
    • Add impl From<*Lit> for pm::Literal (for non-bool literals)
    • Add impl From<BoolLit> for pm::Ident

    Fixed

    • Fix link to reference and clarify bool literals (#7)

    Internals

    • Move lots of parsing code into non-generic functions (this hopefully reduces compile times)
    • To implement [into_]raw_input for integer and float literals, their internals were changed a bit so that they store the full input string now.

    [0.2.3] - 2021-06-09

    Changed

    • Minor internal code change to bring MSRV from 1.52 to 1.42

    [0.2.2] - 2021-06-09

    Changed

    • Fixed (byte) string literal parsing by:
      • Correctly handling "string continue" sequences
      • Correctly converting \n\r into \n

    [0.2.1] - 2021-06-04

    Changed

    • Fixed the expected value of the error returned from TryFrom<TokenTree> impls in some cases

    [0.2.0] - 2021-05-28

    Changed

    • Breaking: rename Error to ParseError. That describes its purpose more closely and is particular useful now that other error types exist in the library.

    Removed

    • Breaking: remove proc-macro feature and instead offer the corresponding impls unconditionally. Since the feature didn't enable/disable a dependency (proc-macro is a compiler provided crate) and since apparently it works fine in no_std environments, I dropped this feature. I don't currently see a reason why the corresponding impls should be conditional.

    Added

    • TryFrom<TokenTree> for litrs::Literal impls
    • From<*Lit> for litrs::Literal impls
    • TryFrom<proc_macro[2]::Literal> for *Lit
    • TryFrom<TokenTree> for *Lit
    • InvalidToken error type for all new TryFrom impls

    ... (truncated)

    Commits
    • 3cff8b4 Bump version to 0.3.0
    • f38decc Fix tests to compile without proc-macro2 feature
    • 70e55b7 Add rust_version to Cargo.toml (it's currently 1.54)
    • 9e86329 Rename master to main
    • 88c95b9 Add inline(always) to some trivial methods that weren't inlined
    • 271ac57 Move lots of parsing code into non-generic functions
    • e9aec00 Add impl From\<BoolLit> for pm::Ident
    • 95af317 Implement From\<*Lit> for pm::Literal (for non-bool literals)
    • 882e780 Add raw_input and into_raw_input to non-bool *Lit types
    • 1654e83 Rewrite FloatLit and IntegerLit to store full input buffer
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies rust 
    opened by dependabot[bot] 3
  • Update litrs requirement from 0.2.3 to 0.3.0 in /custom-format-macros

    Update litrs requirement from 0.2.3 to 0.3.0 in /custom-format-macros

    ⚠️ Dependabot is rebasing this PR ⚠️

    Rebasing might not happen immediately, so don't worry if this takes some time.

    Note: if you make any changes to this PR yourself, they will take precedence over the rebase.


    Updates the requirements on litrs to permit the latest version.

    Release notes

    Sourced from litrs's releases.

    v0.3.0

    Breaking

    • Bump MSRV (minimal supported Rust version) to 1.54

    Added

    • Add raw_input and into_raw_input to non-bool *Lit types
    • Add impl From<*Lit> for pm::Literal (for non-bool literals)
    • Add impl From<BoolLit> for pm::Ident

    Fixed

    • Fix link to reference and clarify bool literals (#7)

    Internals

    • Move lots of parsing code into non-generic functions (this hopefully reduces compile times)
    • To implement [into_]raw_input for integer and float literals, their internals were changed a bit so that they store the full input string now.
    Changelog

    Sourced from litrs's changelog.

    [0.3.0] - 2022-12-19

    Breaking

    • Bump MSRV (minimal supported Rust version) to 1.54

    Added

    • Add raw_input and into_raw_input to non-bool *Lit types
    • Add impl From<*Lit> for pm::Literal (for non-bool literals)
    • Add impl From<BoolLit> for pm::Ident

    Fixed

    • Fix link to reference and clarify bool literals (#7)

    Internals

    • Move lots of parsing code into non-generic functions (this hopefully reduces compile times)
    • To implement [into_]raw_input for integer and float literals, their internals were changed a bit so that they store the full input string now.

    [0.2.3] - 2021-06-09

    Changed

    • Minor internal code change to bring MSRV from 1.52 to 1.42

    [0.2.2] - 2021-06-09

    Changed

    • Fixed (byte) string literal parsing by:
      • Correctly handling "string continue" sequences
      • Correctly converting \n\r into \n

    [0.2.1] - 2021-06-04

    Changed

    • Fixed the expected value of the error returned from TryFrom<TokenTree> impls in some cases

    [0.2.0] - 2021-05-28

    Changed

    • Breaking: rename Error to ParseError. That describes its purpose more closely and is particular useful now that other error types exist in the library.

    Removed

    • Breaking: remove proc-macro feature and instead offer the corresponding impls unconditionally. Since the feature didn't enable/disable a dependency (proc-macro is a compiler provided crate) and since apparently it works fine in no_std environments, I dropped this feature. I don't currently see a reason why the corresponding impls should be conditional.

    Added

    • TryFrom<TokenTree> for litrs::Literal impls
    • From<*Lit> for litrs::Literal impls
    • TryFrom<proc_macro[2]::Literal> for *Lit
    • TryFrom<TokenTree> for *Lit
    • InvalidToken error type for all new TryFrom impls

    ... (truncated)

    Commits
    • 3cff8b4 Bump version to 0.3.0
    • f38decc Fix tests to compile without proc-macro2 feature
    • 70e55b7 Add rust_version to Cargo.toml (it's currently 1.54)
    • 9e86329 Rename master to main
    • 88c95b9 Add inline(always) to some trivial methods that weren't inlined
    • 271ac57 Move lots of parsing code into non-generic functions
    • e9aec00 Add impl From\<BoolLit> for pm::Ident
    • 95af317 Implement From\<*Lit> for pm::Literal (for non-bool literals)
    • 882e780 Add raw_input and into_raw_input to non-bool *Lit types
    • 1654e83 Rewrite FloatLit and IntegerLit to store full input buffer
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies rust 
    opened by dependabot[bot] 2
Owner
Rust, Go, C/C++, Python, Javascript, Dart
null
Rust library of custom number malarkey, including variable-bit-width integers

Numberwang The Numberwang crate is a library of custom number types and functionality, including variable-bit-width integers. It is named after the fi

Dan Williams 3 Nov 12, 2024
Custom deserialization for fields that can be specified as multiple types.

serde-this-or-that Custom deserialization for fields that can be specified as multiple types. This crate works with Cargo with a Cargo.toml like: [dep

Ritvik Nag 7 Aug 25, 2022
proc-macro to help with using surrealdb's custom functions

SurrealDB Functions This is a proc-macro crate that given a path to a .surql file or a folder of .surql files, will parse DEFINE FUNCTION fn::s inside

Aly 5 Jul 30, 2023
Create custom ID types that are guaranteed to be valid `RecordID` in SurrealDB

surreal-id The surreal-id crate offers a standardized way to create and validate IDs in your application for usage with SurrealDB. Using the NewId tra

Liam Woodleigh-Hardinge 4 Oct 5, 2023
Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

Larry Fantasy 635 Jan 3, 2023
Simple autoclicker written in Rust, to learn the Rust language.

RClicker is an autoclicker written in Rust, written to learn more about the Rust programming language. RClicker was was written by me to learn more ab

null 7 Nov 15, 2022
Rust programs written entirely in Rust

mustang Programs written entirely in Rust Mustang is a system for building programs built entirely in Rust, meaning they do not depend on any part of

Dan Gohman 561 Dec 26, 2022
Rust 核心库和标准库的源码级中文翻译,可作为 IDE 工具的智能提示 (Rust core library and standard library translation. can be used as IntelliSense for IDE tools)

Rust 标准库中文版 这是翻译 Rust 库 的地方, 相关源代码来自于 https://github.com/rust-lang/rust。 如果您不会说英语,那么拥有使用中文的文档至关重要,即使您会说英语,使用母语也仍然能让您感到愉快。Rust 标准库是高质量的,不管是新手还是老手,都可以从中

wtklbm 493 Jan 4, 2023
A library for extracting #[no_mangle] pub extern "C" functions (https://docs.rust-embedded.org/book/interoperability/rust-with-c.html#no_mangle)

A library for extracting #[no_mangle] pub extern "C" functions In order to expose a function with C binary interface for interoperability with other p

Dmitrii - Demenev 0 Feb 17, 2022
clone of grep cli written in Rust. From Chapter 12 of the Rust Programming Language book

minigrep is a clone of the grep cli in rust Minigrep will find a query string in a file. To test it out, clone the project and run cargo run body poem

Raunak Singh 1 Dec 14, 2021