00001 /******************************************************************************* 00002 00003 @file Styled.d 00004 00005 Copyright (c) 2004 Kris Bell 00006 00007 This software is provided 'as-is', without any express or implied 00008 warranty. In no event will the authors be held liable for damages 00009 of any kind arising from the use of this software. 00010 00011 Permission is hereby granted to anyone to use this software for any 00012 purpose, including commercial applications, and to alter it and/or 00013 redistribute it freely, subject to the following restrictions: 00014 00015 1. The origin of this software must not be misrepresented; you must 00016 not claim that you wrote the original software. If you use this 00017 software in a product, an acknowledgment within documentation of 00018 said product would be appreciated but is not required. 00019 00020 2. Altered source versions must be plainly marked as such, and must 00021 not be misrepresented as being the original software. 00022 00023 3. This notice may not be removed or altered from any distribution 00024 of the source. 00025 00026 4. Derivative works are permitted, but they must carry this notice 00027 in full and credit the original source. 00028 00029 00030 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00031 00032 00033 @version Initial version, Feb 2005 00034 @author Kris 00035 00036 00037 *******************************************************************************/ 00038 00039 module mango.format.Styled; 00040 00041 version (Ares) 00042 private import std.c.ctype; 00043 else 00044 private import std.ctype; 00045 00046 private import mango.io.Exception; 00047 00048 /****************************************************************************** 00049 00050 Root class of the formatting hierarchy. 00051 00052 While these functions are all static, they are encapsulated within 00053 a class inheritance to preserve some namespace cohesion. One might 00054 use structs for encapsualtion instead, but then inheritance would 00055 be lost. Note that this root class is abstract to prevent accidental 00056 instantiation of subclasses. 00057 00058 See Styled.parse() for format specifiers. 00059 00060 ******************************************************************************/ 00061 00062 abstract class Styled 00063 { 00064 private typedef int delegate (char[]) Utf8Sink; 00065 private typedef int function (double, inout Style) DblFormat; 00066 00067 // array of spaces to pad styled-output 00068 private static char[256] Spaces = ' '; 00069 00070 /********************************************************************** 00071 00072 Declare stylistic flags 00073 00074 **********************************************************************/ 00075 00076 enum Flags 00077 { 00078 Fill = 1, // do some kind of padding 00079 Left = Fill << 1, // left justify 00080 Prec = Left << 1, // precision was provided 00081 Hash = Prec << 1, // prefix integer with type 00082 Space = Hash << 1, // prefix with space 00083 Zero = Space << 1, // prefix integer with zero 00084 Sign = Zero << 1, // unused 00085 Comma = Sign << 1, // unused 00086 Plus = Comma << 1, // prefix decimal with '+' 00087 Array = Plus << 1, // array flag 00088 }; 00089 00090 /********************************************************************** 00091 00092 The state of a stylistic conversion. This can be embedded 00093 within a class, or instantiated on the stack. Be sure to 00094 initialize the Style via methods opCall() & setFormat() 00095 00096 **********************************************************************/ 00097 00098 struct Style 00099 { 00100 Utf8Sink utf8; 00101 int type, 00102 width, 00103 precision; 00104 Flags flags; 00105 char[] head, 00106 tail, 00107 meta; 00108 DblFormat dFormat; 00109 char[] workspace; 00110 00111 /************************************************************** 00112 00113 Configure this Style with an output handler, a 00114 workspace area, and a floating point handler. 00115 The latter is optional. 00116 00117 **************************************************************/ 00118 00119 void opCall (Utf8Sink utf8, char[] workspace, DblFormat dFormat = null) 00120 { 00121 this.utf8 = utf8; 00122 this.dFormat = dFormat; 00123 this.workspace = workspace; 00124 } 00125 00126 /************************************************************** 00127 00128 Set the format string for this Style 00129 00130 **************************************************************/ 00131 00132 void setFormat (char[] format) 00133 { 00134 meta = format; 00135 } 00136 00137 /************************************************************** 00138 00139 Clear the current state. This is typically used 00140 internally only. 00141 00142 **************************************************************/ 00143 00144 void clear () 00145 { 00146 flags = 0; 00147 head = tail = null; 00148 width = workspace.length; 00149 } 00150 00151 /************************************************************** 00152 00153 Emit a field, surrounded by optional prefix and 00154 postfix strings, and optionally padded with spaces. 00155 00156 **************************************************************/ 00157 00158 int emit (char[] field) 00159 { 00160 int i = 0; 00161 int pad = 0; 00162 00163 // emit prefix? 00164 if (head.length) 00165 i += utf8 (head); 00166 00167 // should we pad output? 00168 if (flags & Flags.Fill && flags & Flags.Space) 00169 { 00170 pad = width - field.length; 00171 if (pad < 0) 00172 pad = 0; 00173 else 00174 if (pad > Spaces.length) 00175 error ("space padding greater than 256"); 00176 00177 // right-aligned? 00178 if (flags & Flags.Left == 0) 00179 { 00180 i += utf8 (Spaces[0..pad]); 00181 pad = 0; 00182 } 00183 } 00184 00185 // emit field itself 00186 i += utf8 (field); 00187 00188 // any trailing padding? 00189 if (pad) 00190 i += utf8 (Spaces[0..pad]); 00191 00192 // emit postfix 00193 if (tail.length) 00194 i += utf8 (tail); 00195 00196 return i; 00197 } 00198 00199 /************************************************************** 00200 00201 Parse a format specifier into its constituent 00202 flags and values. Syntax follows the traditional 00203 printf() approach, as follows: 00204 00205 %[flags][width][.precision]type 00206 00207 Where 'type' is one of: 00208 00209 s : string format 00210 c : character format 00211 d : signed format 00212 u : unsigned format 00213 x : hexadecimal format 00214 X : uppercase hexadecimal format 00215 e : scientific notation 00216 f : floating point format 00217 g : 'e' or 'f', based upon width 00218 00219 Note that there are no variants on the format 00220 types ~ long, int, short, and byte differences 00221 are all handled internally. 00222 00223 The 'flags' supported: 00224 00225 space : prefix negative integer with one space; 00226 pad any type when combined with a width 00227 specifier 00228 - : left-align fields padded with spaces 00229 + : prefix positive integer with one '+' 00230 0 : prefix integers with zeroes; requires a 00231 width specification 00232 # : prefix integers with a type specifier 00233 @ : Array specifier 00234 00235 The 'width' should be specified for either zero or 00236 space padding, and may be used with all formatting 00237 types. 00238 00239 A 'precision' can be used to stipulate the number 00240 of decimal-places, or a slice of a text string. 00241 00242 Note that the Format package supports array-output 00243 in addition to the usual printf() output. 00244 00245 **************************************************************/ 00246 00247 char parse (char[] format) 00248 { 00249 clear (); 00250 head = format; 00251 char* p = format.ptr; 00252 00253 for (int i = format.length; --i > 0; ++p) 00254 if (*p == '%') 00255 if (p[1] == '%') 00256 ++p; 00257 else 00258 { 00259 int len = p - format.ptr; 00260 head = format [0..len]; 00261 while (1) 00262 { 00263 switch (--i, *++p) 00264 { 00265 case '-': 00266 flags |= Flags.Left; 00267 continue; 00268 00269 case '+': 00270 flags |= Flags.Plus; 00271 continue; 00272 00273 case '#': 00274 flags |= Flags.Hash; 00275 continue; 00276 00277 case ' ': 00278 flags |= Flags.Space; 00279 continue; 00280 00281 case '0': 00282 flags |= Flags.Zero; 00283 continue; 00284 00285 case '@': 00286 flags |= Flags.Array; 00287 continue; 00288 00289 default: 00290 ++i; 00291 break; 00292 } 00293 break; 00294 } 00295 00296 if (isdigit(*p)) 00297 { 00298 int tmp; 00299 do { 00300 tmp = tmp * 10 + (*p - '0'); 00301 } while (--i && isdigit(*++p)); 00302 00303 flags |= Flags.Fill; 00304 width = tmp; 00305 } 00306 else 00307 flags &= ~Flags.Zero; 00308 00309 00310 if (*p == '.') 00311 { 00312 int tmp; 00313 while (i-- && isdigit(*++p)) 00314 tmp = tmp * 10 + (*p - '0'); 00315 00316 flags |= Flags.Prec; 00317 precision = tmp; 00318 } 00319 00320 if (--i < 0) 00321 error ("missing format specifier"); 00322 00323 tail = format [format.length-i..format.length]; 00324 return type = *p; 00325 } 00326 00327 return type = 0; 00328 } 00329 } 00330 00331 00332 /********************************************************************** 00333 00334 Throw an exception with the provided messsage 00335 00336 **********************************************************************/ 00337 00338 final static void error (char[] msg) 00339 { 00340 throw new FormatException (msg); 00341 } 00342 } 00343 00344 00345 /******************************************************************************* 00346 00347 This exception is thrown by the Format package if it finds issue 00348 with the format specifiers and so on. 00349 00350 *******************************************************************************/ 00351 00352 class FormatException : Exception 00353 { 00354 /*********************************************************************** 00355 00356 Construct exception with the provided text string 00357 00358 ***********************************************************************/ 00359 00360 this (char[] msg) 00361 { 00362 super (msg); 00363 } 00364 } 00365