ggformat

A string formatting library for C++
Log | Files | Refs | README

README.md (6848B)


      1 # ggformat v1.0
      2 
      3 ggformat is a liberally licensed string formatting library for C++ that
      4 supports user defined types without blowing up your compile times. It is
      5 meant to be used as a replacement for printf and friends.
      6 
      7 ggformat saves you time by reducing the amount of tedious boilerplate
      8 code you have to write, without adding that time back onto the build.
      9 ggformat has a trivial API and does not mind being mixed with other
     10 formatting libraries, so it's easy to incrementally integrate with
     11 existing software. In particular, it's easy to integrate ggformat with
     12 your favourite string class, adding an extra convenience API on top of
     13 the functionality you already use. ggformat does not allocate memory
     14 unless you want it to, and it's easy to use your own allocators, making
     15 it appropriate for use in long-lived applications and games where memory
     16 fragmentation is a concern.
     17 
     18 ggformat requires C++11 (variadic templates), and supports VS2015, GCC
     19 and clang out of the box. It should also work with VS2013 and VS2017 but
     20 I don't test against them.
     21 
     22 I wrote ggformat because the existing string formatting options for C++
     23 either do not support user defined types or bloat compile times too
     24 much. printf doesn't support user defined types. Streams and std::string
     25 are slow to compile and IO manipulators are unreadable. Other C++
     26 formatting libraries include STL headers and also hurt compile times.
     27 
     28 
     29 ## Version history
     30 
     31 - __v1.0 29th Oct 2017__: variadic arguments are now passed by const
     32   reference. You can now use ggformat with types that have deleted copy
     33   constructors/assignment operators.
     34 
     35 
     36 ## Usage
     37 
     38 ```cpp
     39 size_t ggformat( char * buf, size_t len, const char * fmt, ... );
     40 bool ggprint_to_file( FILE * file, const char * fmt, ... );
     41 bool ggprint( const char * fmt, ... );
     42 ```
     43 
     44 In short, `ggformat` replaces s(n)printf, `ggprint_to_file` replaces
     45 fprintf, and `ggprint` replaces printf.
     46 
     47 `ggformat` writes at most `len` bytes to `buf`, and that always includes
     48 a null terminator. It returns the number of bytes that would have been
     49 written if `buf` were large enough, _not including the null terminator_,
     50 and can be larger than `len` (just like sprintf).
     51 
     52 `ggprint_to_file` does what you would expect, and `ggprint` writes to
     53 standard output. Both return `true` on success, or `false` if the write
     54 fails.
     55 
     56 `ggformat` does not allocate memory, the other functions allocate if you
     57 print lots of data. All functions abort if you mess up the formatting
     58 string or don't pass the right number of arguments. You should not pass
     59 user defined strings as format strings, and I believe it's more helpful
     60 to fail hard on programmer typos.
     61 	
     62 Basic usage looks like this:
     63 
     64 ```cpp
     65 #include "ggformat.h"
     66 
     67 int main() {
     68 	ggprint( "hello {}\n", 1.23 ); // hello 1.23000
     69 	return 0;
     70 }
     71 ```
     72 
     73 and you can see more examples in basic_examples.cc and
     74 string_examples.cc.
     75 
     76 
     77 ## Format options
     78 
     79 You can add format specifiers between the braces to change how things
     80 are printed. The following options are supported:
     81 
     82 - Plus sign (`{+}`): Prints a leading + for positive numeric types.
     83 - Width (`{x}`): left pads the output with spaces to be `x` characters
     84   wide. When used on floats, it left pads the output so the _left side
     85   of the decimal point_ is `x` characters wide (I chose this because I
     86   think it makes `{x.y}` more intuitive). If the output is already wider
     87   than `x` characters, it doesn't do anything.
     88 - Width with zero padding (`{0x}`): as above, but pads with zeroes
     89   instead of spaces.
     90 - Width with left alignment (`{-x}` or `{-0x}`): same again but puts the
     91   spaces/zeroes on the right.
     92 - Precision (`{.x}`): specifies the number of digits that appear after
     93   the decimal point when printing floats.
     94 - Number format (`{x}` or `{b}`): prints integers in hexadecimal/binary.
     95 
     96 These can all be combined, but should be kept in the order they were
     97 just listed in.
     98 
     99 If you want to print a literal { or }, use {{ and }}.
    100 
    101 
    102 ## User defined types
    103 
    104 If you want to print your own types with ggformat, you need to define
    105 `void format( FormatBuffer * fb, T x, const FormatOpts & opts );`.
    106 `FormatBuffer` is a wrapper around a `char *` and length and its exact
    107 definition is not important. `FormatOpts` holds parsed format options
    108 and is defined as:
    109 
    110 ```cpp
    111 struct FormatOpts {
    112         enum NumberFormat { DECIMAL, HEX, BINARY };
    113 
    114         int width = -1;
    115         int precision = -1;
    116         bool plus_sign = false;
    117         bool left_align = false;
    118         bool zero_pad = false;
    119         NumberFormat number_format = DECIMAL;
    120 };
    121 ```
    122 
    123 `format` implementations are typically quite simple:
    124 
    125 ```cpp
    126 #include "ggformat.h"
    127 
    128 struct v3 {
    129 	explicit v3( float x_, float y_, float z_ ) { x = x_; y = y_; z = z_; }
    130 	float x, y, z;
    131 };
    132 
    133 v3 operator+( const v3 & lhs, const v3 & rhs ) {
    134 	return v3( lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z );
    135 }
    136 
    137 void format( FormatBuffer * fb, const v3 & v, const FormatOpts & opts ) {
    138 	format( fb, "v3(" );
    139 	format( fb, v.x, opts );
    140 	format( fb, ", " );
    141 	format( fb, v.y, opts );
    142 	format( fb, ", " );
    143 	format( fb, v.z, opts );
    144 	format( fb, ")" );
    145 }
    146 
    147 int main() {
    148         v3 a = v3( 1, 2, 3 );
    149         v3 b = v3( 4, 5, 6 );
    150 	// a = v3(1.00000, 2.00000, 3.00000). b = v3( 4.00,  5.00,  6.00).
    151 	// a + b = v3(+5.00000, +7.00000, +9.00000)
    152         ggprint( "a = {}. b = {2.2}.\na + b = {+}\n", a, b, a + b );
    153 
    154         return 0;
    155 }
    156 ```
    157 
    158 If you have a huge type and don't feel like writing a wall of `format`,
    159 see `Thing` in basic_examples.cc.
    160 
    161 
    162 ## Dynamic allocation (std::string, asprintf, etc)
    163 
    164 `ggformat( NULL, 0, ... );` returns the number of bytes required to hold
    165 the formatted string. With that it's easy to integrate ggformat with
    166 your favourite dynamic string solution. For example, ggformat with
    167 std::string:
    168 
    169 ```cpp
    170 template< typename... Rest >
    171 std::string ggformat_to_string( const char * fmt, const Rest & ... rest ) {
    172 	size_t space_required = ggformat( nullptr, 0, fmt, rest... );
    173 
    174 	if( space_required + 1 < space_required )
    175 		throw std::overflow_error( "formatted string is too long" );
    176 
    177 	std::string result;
    178 	result.resize( space_required + 1 ); // + 1 so there's space for the null terminator...
    179 	ggformat( &result[ 0 ], space_required + 1, fmt, rest... );
    180 	result.resize( space_required ); // ...and then trim it off
    181 
    182 	return result;
    183 }
    184 ```
    185 
    186 
    187 ## Other stuff
    188 
    189 Since this is C++ you can and should wrap `ggformat` in a string class
    190 to make it more convenient to use. You can see an example in
    191 string_examples.cc.
    192 
    193 ggformat uses sprintf under the hood. sprintf can be pretty slow at
    194 runtime, but compiles quickly.
    195 
    196 In general ggformat is short enough that you can easily modify it to fit
    197 your needs, and will be updated infrequently enough that doing so isn't
    198 a huge pain. For example, it's very easy to replace malloc/free with
    199 your own allocators, and if you don't like aborting on errors it's
    200 pretty easy to change that too.