Changeset 53:f000d6cd0f74
- Timestamp:
- 06/05/08 12:16:52 (7 months ago)
- Files:
-
- .hgignore (added)
- codeDoc/jobs.txt (modified) (1 diff)
- data/conf/options.mtt (modified) (2 diffs)
- mde/Options.d (modified) (1 diff)
- mde/mde.d (modified) (2 diffs)
- mde/resource/FontTexture.d (modified) (10 diffs)
- mde/resource/font.d (modified) (1 diff)
- mde/resource/paths.d (modified) (8 diffs)
- mde/scheduler/Init.d (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
codeDoc/jobs.txt
r50 r53 53 53 54 54 Done (for git log message): 55 Options: impl template to ease creating Options sub-classes.56 New OptionsFont class (currently only controls render mode and hinting).data/conf/options.mtt
r51 r53 5 5 <char[]|L10n="en-GB"> 6 6 <int|logLevel=1> 7 <int|logOptions=0x1003> 7 8 <double|pollInterval=0.01> 8 9 9 10 {font} 10 <int|lcdFilter= 0>11 <int|renderMode=0 >11 <int|lcdFilter=2> 12 <int|renderMode=0x30000> 12 13 13 14 {video} … … 17 18 <bool|fullscreen=false> 18 19 <int|screenW=1280> 19 <int|windowW= 1272>20 <int|windowW=800> 20 21 <int|screenH=1024> 21 <int|windowH= 993>22 <int|windowH=600> 22 23 mde/Options.d
r50 r53 476 476 OptionsMisc miscOpts; 477 477 class OptionsMisc : Options { 478 mixin (impl!("bool useThreads, exitImmediately; int log Level; double pollInterval; char[] L10n;"));478 mixin (impl!("bool useThreads, exitImmediately; int logOptions; double pollInterval; char[] L10n;")); 479 479 480 480 static this() { mde/mde.d
r45 r53 37 37 import tango.util.log.Log : Log, Logger; 38 38 39 int main( )39 int main(char[][] args) 40 40 { 41 41 //BEGIN Initialisation 42 42 Logger logger = Log.getLogger ("mde.mde"); 43 logger.info ("Starting mde...");44 43 45 44 // Create instances now, so they can be used during init (if necessary) … … 49 48 scope Init init; 50 49 try { 51 init = new Init( ); // initialisation50 init = new Init(args); // initialisation 52 51 } catch (InitException e) { 53 52 logger.fatal ("Initialisation failed: " ~ e.msg); mde/resource/FontTexture.d
r51 r53 33 33 import Utf = tango.text.convert.Utf; 34 34 import tango.util.log.Log : Log, Logger; 35 import tango.io.Stdout;36 35 37 36 private Logger logger; … … 75 74 { 76 75 debug scope (failure) 77 logger. warn("updateCache failed");76 logger.error ("updateCache failed"); 78 77 79 78 if (cache.cacheVer == cacheVer) // Existing cache is up-to-date … … 166 165 updateCache (face, str, cache); // update if necessary 167 166 debug scope (failure) 168 logger. warn("drawTextCache failed");167 logger.error ("drawTextCache failed"); 169 168 170 169 glEnable (GL_TEXTURE_2D); … … 199 198 void addGlyph (FT_Face face, dchar chr) { 200 199 debug scope (failure) 201 logger. warn("FontTexture.addGlyph failed!");200 logger.error ("FontTexture.addGlyph failed!"); 202 201 203 202 auto gi = FT_Get_Char_Index (face, chr); … … 209 208 210 209 auto b = g.bitmap; 211 if (b.pitch != b.width) {212 char[128] tmp;213 logger.warn (logger.format (tmp, "b.pitch is {}, b.width is {}", b.pitch, b.width));214 //throw new fontGlyphException ("Unsupported freetype bitmap: b.pitch != b.width");215 }216 210 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 217 211 //glPixelStorei (GL_UNPACK_ROW_LENGTH, b.pitch); … … 235 229 // if here, no existing texture had the room for the glyph so create a new texture 236 230 // NOTE: check if using more than one texture impacts performance due to texture switching 237 logger.info ("Creating a font texture.");231 logger.info ("Creating a new font texture."); 238 232 tex ~= TexPacker.create(); 239 233 assert (tex[$-1].addGlyph (ga), "Failed to fit glyph in a new texture but addGlyph didn't throw"); … … 244 238 ubyte[] buffer; // use our own pointer, since for LCD modes we need to perform a conversion 245 239 if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_GRAY && b.num_grays == 256) { 240 assert (b.pitch == b.width, "Have assumed b.pitch == b.width for gray glyphs."); 246 241 buffer = b.buffer[0..b.pitch*b.rows]; 247 242 format = GL_LUMINANCE; … … 358 353 throw new fontGlyphException ("Glyph too large to fit texture!"); 359 354 355 attr.texID = texID; // Set now. Possibly reset if new texture is needed. 356 if (attr.w == 0) return true; // 0 sized glyph; x and y are unimportant. 357 360 358 bool cantFitExtraLine = nextYPos + attr.h >= dimH; 361 359 foreach (ref line; lines) { … … 381 379 line.yPos = nextYPos; 382 380 line.height = attr.h * EXTRA_H; 383 Stdout.format ("Creating new line of height {}", line.height).newline;384 381 line.length = attr.w; 385 382 size_t i = 0; … … 390 387 attr.x = 0; // first glyph in the line 391 388 attr.y = line.yPos; 392 attr.texID = texID;393 389 return true; 394 390 } mde/resource/font.d
r51 r53 72 72 } 73 73 74 // Set LCD filtering method (note: FT_Library_SetLcdFilter can still complain with 75 // FT_LcdFilter.FT_LCD_FILTER_NONE). 76 if (fontOpts.lcdFilter && FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) { 77 // If setting failed, leave at default (disabled status). Note: it is disabled by 78 // default because the code isn't compiled in by default, to avoid patents. 79 logger.warn ("Bad/unsupported LCD filter option; disabling LCD filtering."); 74 // Set LCD filtering method if LCD rendering is enabled. 75 const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; 76 if (fontOpts.renderMode & RMF && 77 FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) { 78 /* An error occurred, presumably because LCD rendering support 79 * is not compiled into the library. */ 80 logger.warn ("Bad/unsupported LCD filter option; disabling LCD font rendering."); 80 81 logger.warn ("Your FreeType 2 library may compiled without support for LCD/sub-pixel rendering."); 81 Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_NONE); 82 } 83 const RMF = FT_LOAD_TARGET_MONO | FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; 84 if (fontOpts.renderMode & RMF && fontOpts.lcdFilter == 0) { 85 logger.warn ("Using LCD rendering when LCD filtering is disabled has no effect; disabling."); 82 83 // Reset the default filter (in case an invalid value was set in config files). 84 Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT); 85 86 /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3 87 * times wider glyphs. So disable and save the extra work. */ 86 88 Options.setInt ("font", "renderMode", FT_LOAD_TARGET_NORMAL); 87 89 } mde/resource/paths.d
r47 r53 40 40 import tango.io.Console; 41 41 import tango.io.FilePath; 42 import tango.stdc.stdlib; 43 import tango.stdc.stringz; 42 import tango.sys.Environment; 44 43 //import tango.scrapple.sys.win32.Registry; // Trouble getting this to work 45 44 … … 116 115 } 117 116 117 /// Print all paths found. 118 static void printPaths () { 119 Cout ("Data paths found:"); 120 dataDir.coutPaths; 121 Cout ("\nConf paths found:"); 122 confDir.coutPaths; 123 Cout ("\nLog file directory:\n\t")(logDir).newline; 124 } 125 118 126 private: 119 127 PathView[] getFiles (char[] filename, PRIORITY readOrder) … … 166 174 } 167 175 176 void coutPaths () { 177 if (pathsLen) { 178 for (size_t i = 0; i < pathsLen; ++i) 179 Cout ("\n\t" ~ paths[i]); 180 } else 181 Cout ("[none]"); 182 } 183 168 184 // Use a static array to store all possible paths with separate length counters. 169 185 // Lowest priority paths are first. … … 186 202 // FIXME: use tango/sys/Environment.d 187 203 version (linux) { 188 void resolvePaths () { 204 // base-path not used on posix 205 void resolvePaths (char[] = null) { 189 206 // Home directory: 190 char[] HOME = fromStringz (getenv (toStringz ("HOME")));207 char[] HOME = Environment.get("HOME", "."); 191 208 192 209 // Base paths: 193 210 // Static data (must exist): 194 PathView staticPath = findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); 211 PathView staticPath = 212 findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); 195 213 // Config (can just use defaults if necessary, so long as we can save afterwards): 196 214 PathView userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde"); … … 199 217 dataDir.addPath (staticPath.toString); // we know this is valid anyway 200 218 dataDir.tryPath (userPath.toString ~ DATA); 219 if (extraDataPath) dataDir.tryPath (extraDataPath); 201 220 if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); 202 221 … … 205 224 confDir.tryPath ("/etc/mde"); 206 225 confDir.tryPath (userPath.toString ~ CONF, true); 226 if (extraConfPath) confDir.tryPath (extraConfPath); 207 227 if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); 208 228 … … 211 231 } 212 232 } else version (Windows) { 213 void resolvePaths ( ) {233 void resolvePaths (char[] base = "./") { 214 234 //FIXME: Get path from registry 215 235 //FIXME: Get user path (Docs&Settings/USER/Local Settings/Application data/mde) 236 //http://www.dsource.org/projects/tango/forums/topic/187 216 237 217 238 // Base paths: 218 PathView installPath = findPath (false, "");;219 PathView userPath = findPath (true, "user"); // FIXME: see above220 PathView staticPath = findPath (false, "data");239 PathView installPath = findPath (false, base); 240 PathView staticPath = findPath (false, installPath.append("data").toString); 241 PathView userPath = findPath (true, installPath.append("user").toString); // FIXME: see above 221 242 222 243 // Static data paths: 223 244 dataDir.addPath (staticPath.toString); // we know this is valid anyway 224 245 dataDir.tryPath (userPath.toString ~ DATA); 246 if (extraDataPath) dataDir.tryPath (extraDataPath); 225 247 if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); 226 248 227 249 // Configuration paths: 228 250 confDir.tryPath (staticPath.toString ~ CONF); 229 confDir.tryPath ( "conf");251 confDir.tryPath (installPath.append("user").toString); 230 252 confDir.tryPath (userPath.toString ~ CONF, true); 253 if (extraConfPath) confDir.tryPath (extraConfPath); 231 254 if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); 232 255 … … 238 261 } 239 262 240 private: 263 /// For command line args: these paths are added if non-null, with highest priority. 264 char[] extraDataPath, extraConfPath; 265 266 private { 267 class PathException : mdeException { 268 this(char[] msg) { 269 super (msg); 270 } 271 } 272 241 273 // The maximum number of paths for any one "directory". 242 274 // There are NO CHECKS that this is not exceeded. 243 const MAX_PATHS = 3; 244 245 /* Try each path in succession, returning the first to exist and be a folder. 246 * If none are valid and create is true, will try creating each in turn. 247 * If still none are valid, throws. */ 248 PathView findPath (bool create, char[][] paths ...) { 249 foreach (path; paths) { 250 PathView pv = new FilePath (path); 251 if (pv.exists && pv.isFolder) return pv; // got a valid path 252 } 253 if (create) { // try to create a folder, using each path in turn until succesful 254 foreach (path; paths) { 255 FilePath fp = new FilePath (path); 256 try { 257 return fp.create; 258 } 259 catch (Exception e) {} 260 } 261 } 275 const MAX_PATHS = 4; 276 277 /* Try each path in succession, returning the first to exist and be a folder. 278 * If none are valid and create is true, will try creating each in turn. 279 * If still none are valid, throws. */ 280 PathView findPath (bool create, char[][] paths ...) { 281 FilePath[] fps; 282 fps.length = paths.length; 283 foreach (i,path; paths) { 284 FilePath pv = new FilePath (path); 285 if (pv.exists && pv.isFolder) return pv; // got a valid path 286 fps[i] = pv; 287 } 288 if (create) { // try to create a folder, using each path in turn until succesful 289 foreach (fp; fps) { 290 try { 291 return fp.create; 292 } 293 catch (Exception e) {} 294 } 295 } 262 296 // no valid path... 263 char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:";264 foreach (path; paths) msg ~= " \"" ~ path ~ '\"';265 throw new mdeException (msg);266 }297 char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; 298 foreach (path; paths) msg ~= " \"" ~ path ~ '\"'; 299 throw new PathException (msg); 300 } 267 301 //END Path resolution 268 302 269 /** A special adapter for reading from multiple mergetag files with the same relative path to an270 * mdeDirectory simultaneously.271 */272 class mdeReader : IReader273 {274 private this (PathView[] files, DataSet ds, bool rdHeader)275 in {276 assert (files !is null, "mdeReader.this: files is null");277 } body {278 if (ds is null) ds = new DataSet;279 280 foreach (file; files) {281 IReader r = makeReader (file, ds, rdHeader);303 /** A special adapter for reading from multiple mergetag files with the same relative path to an 304 * mdeDirectory simultaneously. 305 */ 306 class mdeReader : IReader 307 { 308 private this (PathView[] files, DataSet ds, bool rdHeader) 309 in { 310 assert (files !is null, "mdeReader.this: files is null"); 311 } body { 312 if (ds is null) ds = new DataSet; 313 314 foreach (file; files) { 315 IReader r = makeReader (file, ds, rdHeader); 282 316 283 readers[readersLen++] = r;284 }285 }286 287 DataSet dataset () { /// Get the DataSet288 return readers[0].dataset; // all readers share the same dataset289 }290 void dataset (DataSet ds) { /// Set the DataSet291 for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds);292 }293 294 void dataSecCreator (IDataSection delegate (ID) dsC) { /// Set the dataSecCreator295 for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC;296 }317 readers[readersLen++] = r; 318 } 319 } 320 321 DataSet dataset () { /// Get the DataSet 322 return readers[0].dataset; // all readers share the same dataset 323 } 324 void dataset (DataSet ds) { /// Set the DataSet 325 for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds); 326 } 327 328 void dataSecCreator (IDataSection delegate (ID) dsC) { /// Set the dataSecCreator 329 for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC; 330 } 297 331 298 332 /** Get identifiers for all sections. 299 *300 * Note: the identifiers from all sections in all files are just strung together, starting with301 * the highest-priority file. */333 * 334 * Note: the identifiers from all sections in all files are just strung together, starting with 335 * the highest-priority file. */ 302 336 ID[] getSectionNames () { 303 337 ID[] names; 304 338 for (int i = readersLen-1; i >= 0; --i) names ~= readers[i].getSectionNames; 305 339 return names; 306 } 307 void read () { /// Commence reading 308 for (uint i = 0; i < readersLen; ++i) readers[i].read(); 309 } 310 void read (ID[] secSet) { /// ditto 311 for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 312 } 313 void read (View!(ID) secSet) { /// ditto 314 for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 315 } 316 317 private: 318 IReader[MAX_PATHS] readers; 319 ubyte readersLen = 0; 320 321 PRIORITY rdOrder; 340 } 341 void read () { /// Commence reading 342 for (uint i = 0; i < readersLen; ++i) readers[i].read(); 343 } 344 void read (ID[] secSet) { /// ditto 345 for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 346 } 347 void read (View!(ID) secSet) { /// ditto 348 for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 349 } 350 351 private: 352 IReader[MAX_PATHS] readers; 353 ubyte readersLen = 0; 354 355 PRIORITY rdOrder; 356 } 322 357 } mde/scheduler/Init.d
r45 r53 35 35 import tango.core.Exception; 36 36 import tango.stdc.stringz : fromStringz; 37 import tango.io.Console; // for printing command-line usage 38 import TimeStamp = tango.text.convert.TimeStamp, tango.time.WallClock; // output date in log file 39 import tango.util.Arguments; 37 40 import tango.util.log.Log : Log, Logger; 38 41 import tango.util.log.ConsoleAppender : ConsoleAppender; … … 59 62 static this() 60 63 { 61 // Find/create paths: 62 try { 63 paths.resolvePaths(); 64 } catch (Exception e) { 65 // NOTE: an exception thrown here cannot be caught by main()! 66 throw new InitException ("Resolving paths failed: " ~ e.msg); 67 } 68 69 // Set up the logger: 70 { 71 // Where logging is done to is determined at compile-time, currently just via static ifs. 72 Logger root = Log.getRootLogger(); 73 74 static if (true ) { // Log to files (first appender so root seperator messages don't show on console) 75 version (SwitchAppender) { 76 root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); 77 } else { 78 // Use 2 log files with a maximum size of 1 MB: 79 root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); 80 root.info (""); // some kind of separation between runs 81 root.info (""); 82 } 83 } 84 static if (true ) { // Log to the console 85 root.addAppender(new ConsoleAppender); 86 } 87 88 // Set the level here, but set it again once options have been loaded: 89 debug root.setLevel(root.Level.Trace); 90 else root.setLevel(root.Level.Info); 91 } 64 Logger root = Log.getRootLogger(); 65 // Set the level here, but set it again once options have been loaded: 66 debug root.setLevel(root.Level.Trace); 67 else root.setLevel(root.Level.Info); 68 // Temporarily log to the console (until we've found paths and loaded options): 69 root.addAppender(new ConsoleAppender); 92 70 } 93 71 static ~this() … … 125 103 * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct. 126 104 */ 127 this( )105 this(char[][] cmdArgs) 128 106 { 129 107 debug logger.trace ("Init: starting"); 130 108 131 109 //BEGIN Pre-init (stage init0) 110 //FIXME: warn on invalid arguments, including base-path on non-Windows 111 // But Arguments doesn't support this (in tango 0.99.6 and in r3563). 112 Arguments args; 113 try { 114 args = new Arguments(); 115 args.define("base-path").parameters(1); 116 args.define("data-path").parameters(1,-1); 117 args.define("conf-path").parameters(1,-1); 118 args.define("paths"); 119 args.define("q").aliases(["quick-exit"]); 120 args.define("help").aliases(["h"]); 121 args.parse(cmdArgs); 122 if (args.contains("help")) // lazy way to print help 123 throw new InitException ("Help requested"); // and stop 124 } catch (Exception e) { 125 printUsage(cmdArgs[0]); 126 throw new InitException ("Parsing arguments failed: "~e.msg); 127 } 128 129 // Find/create paths: 130 try { 131 if (args.contains("data-path")) 132 paths.extraDataPath = args["data-path"]; 133 if (args.contains("conf-path")) 134 paths.extraConfPath = args["conf-path"]; 135 if (args.contains("base-path")) 136 paths.resolvePaths (args["base-path"]); 137 else 138 paths.resolvePaths(); 139 } catch (Exception e) { 140 throw new InitException ("Resolving paths failed: " ~ e.msg); 141 } 142 if (args.contains("paths")) { 143 paths.mdeDirectory.printPaths; 144 throw new InitException ("Paths requested"); // lazy way to stop 145 } 146 debug logger.trace ("Init: resolved paths successfully"); 147 132 148 /* Load options now. Don't load in a thread since: 133 * Loading should be fast 134 * Most work is probably disk access135 * It's a really good idea to let the options apply to all other loading .*/149 * Loading should be fast & most work is probably disk access 150 * It enables logging to be controlled by options 151 * It's a really good idea to let the options apply to all other loading */ 136 152 try { 137 153 Options.load(); … … 139 155 throw new InitException ("Loading options failed: " ~ e.msg); 140 156 } 141 142 // Now re-set the logging level, using the value from the config file: 143 Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); 144 // And set this (debug option): 145 imde.run = !miscOpts.exitImmediately; 146 //END Pre-init 147 148 debug logger.trace ("Init: pre-init done"); 157 debug logger.trace ("Init: loaded options successfully"); 158 159 // Set up the logger: 160 Logger root; 161 try { 162 enum LOG { 163 LEVEL = 0x10, // mask for log level 164 CONSOLE = 0x1001, // log to console? 165 ROLLFILE= 0x1002 // use Rolling/Switching File Appender? 166 } 167 168 // Where logging is done to is determined at compile-time, currently just via static ifs. 169 root = Log.getRootLogger(); 170 root.clearAppenders; // we may no longer want to log to the console 171 172 // Now re-set the logging level, using the value from the config file: 173 Log.getRootLogger.setLevel (cast(Log.Level) (miscOpts.logOptions & LOG.LEVEL), true); 174 175 // Log to a file (first appender so root seperator messages don't show on console): 176 if (miscOpts.logOptions & LOG.ROLLFILE) { 177 version (SwitchAppender) { 178 root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); 179 } else { 180 // Use 2 log files with a maximum size of 1 MB: 181 root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); 182 root.info (""); // some kind of separation between runs 183 root.info (""); 184 } 185 } else if (!(miscOpts.logOptions & LOG.CONSOLE)) { 186 // make sure at least one logger is enabled 187 Options.setInt ("misc", "logOptions", miscOpts.logOptions | LOG.CONSOLE); 188 } 189 if (miscOpts.logOptions & LOG.CONSOLE) { // Log to the console 190 root.addAppender(new ConsoleAppender); 191 } 192 logger.info ("Starting mde [no version] on " ~ TimeStamp.toString(WallClock.now)); 193 } catch (Exception e) { 194 // Presumably it was only adding a file appender which failed; set up a new console 195 // logger and if that fails let the exception kill the program. 196 root.clearAppenders; 197 root.addAppender (new ConsoleAppender); 198 logger.warn ("Exception while setting up the logger; logging to the console instead."); 199 } 200 201 // a debugging option: 202 imde.run = !args.contains("q") && !miscOpts.exitImmediately; 203 debug logger.trace ("Init: applied pre-init options"); 149 204 150 205 //BEGIN Load dynamic libraries … … 161 216 throw new InitException ("Loading dynamic libraries failed (see above)."); 162 217 } 218 debug logger.trace ("Init: dynamic libraries loaded"); 163 219 //END Load dynamic libraries 164 165 debug logger.trace ("Init: dynamic libraries loaded");220 //END Pre-init 221 166 222 167 223 //BEGIN Init (stages init2, init4) … … 294 350 return false; // Done successfully 295 351 } 352 //END runStage... 353 354 void printUsage (char[] progName) { 355 Cout ("mde [no version]").newline; 356 Cout ("Usage:").newline; 357 Cout (progName ~ ` [options]`).newline; 358 version(Windows) 359 Cout ( 360 ` --base-path path Use path as the base (install) path (Windows only). It 361 should contain the "data" directory.`).newline; 362 Cout ( 363 ` --data-path path(s) Add path(s) as a potential location for data files. 364 First path argument becomes the preffered location to 365 load data files from. 366 --conf-path path(s) Add path(s) as a potential location for config files. 367 Configuration in the first path given take highest 368 priority. 369 --paths Print all paths found and exit. 370 --quick-exit, -q Exit immediately, without entering main loop. 371 --help, -h Print this message.`).newline; 372 } 296 373 } 297 //END runStage...298 374 299 375 debug (mdeUnitTest) unittest {
