VirtualBox

source: vbox/trunk/src/bldprogs/scm.cpp@ 26516

Last change on this file since 26516 was 26516, checked in by vboxsync, 15 years ago

scm: --only-svn-dirs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 109.2 KB
Line 
1/* $Id: scm.cpp 26516 2010-02-14 21:37:33Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 *
26 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
27 * Clara, CA 95054 USA or visit http://www.sun.com if you need
28 * additional information or have any questions.
29 */
30
31/*******************************************************************************
32* Header Files *
33*******************************************************************************/
34#include <iprt/assert.h>
35#include <iprt/ctype.h>
36#include <iprt/dir.h>
37#include <iprt/file.h>
38#include <iprt/err.h>
39#include <iprt/getopt.h>
40#include <iprt/initterm.h>
41#include <iprt/mem.h>
42#include <iprt/message.h>
43#include <iprt/param.h>
44#include <iprt/path.h>
45#include <iprt/stream.h>
46#include <iprt/string.h>
47
48
49/*******************************************************************************
50* Defined Constants And Macros *
51*******************************************************************************/
52/** The name of the settings files. */
53#define SCM_SETTINGS_FILENAME ".scm-settings"
54
55
56/*******************************************************************************
57* Structures and Typedefs *
58*******************************************************************************/
59/** Pointer to const massager settings. */
60typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
61
62/** End of line marker type. */
63typedef enum SCMEOL
64{
65 SCMEOL_NONE = 0,
66 SCMEOL_LF = 1,
67 SCMEOL_CRLF = 2
68} SCMEOL;
69/** Pointer to an end of line marker type. */
70typedef SCMEOL *PSCMEOL;
71
72/**
73 * Line record.
74 */
75typedef struct SCMSTREAMLINE
76{
77 /** The offset of the line. */
78 size_t off;
79 /** The line length, excluding the LF character.
80 * @todo This could be derived from the offset of the next line if that wasn't
81 * so tedious. */
82 size_t cch;
83 /** The end of line marker type. */
84 SCMEOL enmEol;
85} SCMSTREAMLINE;
86/** Pointer to a line record. */
87typedef SCMSTREAMLINE *PSCMSTREAMLINE;
88
89/**
90 * Source code massager stream.
91 */
92typedef struct SCMSTREAM
93{
94 /** Pointer to the file memory. */
95 char *pch;
96 /** The current stream position. */
97 size_t off;
98 /** The current stream size. */
99 size_t cb;
100 /** The size of the memory pb points to. */
101 size_t cbAllocated;
102
103 /** Line records. */
104 PSCMSTREAMLINE paLines;
105 /** The current line. */
106 size_t iLine;
107 /** The current stream size given in lines. */
108 size_t cLines;
109 /** The sizeof the the memory backing paLines. */
110 size_t cLinesAllocated;
111
112 /** Set if write-only, clear if read-only. */
113 bool fWriteOrRead;
114 /** Set if the memory pb points to is from RTFileReadAll. */
115 bool fFileMemory;
116 /** Set if fully broken into lines. */
117 bool fFullyLineated;
118
119 /** Stream status code (IPRT). */
120 int rc;
121} SCMSTREAM;
122/** Pointer to a SCM stream. */
123typedef SCMSTREAM *PSCMSTREAM;
124/** Pointer to a const SCM stream. */
125typedef SCMSTREAM const *PCSCMSTREAM;
126
127
128/**
129 * Rewriter state.
130 */
131typedef struct SCMRWSTATE
132{
133 /** The filename. */
134 const char *pszFilename;
135 /** Set after the printing the first verbose message about a file under
136 * rewrite. */
137 bool fFirst;
138} SCMRWSTATE;
139/** Pointer to the rewriter state. */
140typedef SCMRWSTATE *PSCMRWSTATE;
141
142/**
143 * A rewriter.
144 *
145 * This works like a stream editor, reading @a pIn, modifying it and writing it
146 * to @a pOut.
147 *
148 * @returns true if any changes were made, false if not.
149 * @param pIn The input stream.
150 * @param pOut The output stream.
151 * @param pSettings The settings.
152 */
153typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
154
155
156/**
157 * Configuration entry.
158 */
159typedef struct SCMCFGENTRY
160{
161 /** Number of rewriters. */
162 size_t cRewriters;
163 /** Pointer to an array of rewriters. */
164 PFNSCMREWRITER const *papfnRewriter;
165 /** File pattern (simple). */
166 const char *pszFilePattern;
167} SCMCFGENTRY;
168typedef SCMCFGENTRY *PSCMCFGENTRY;
169typedef SCMCFGENTRY const *PCSCMCFGENTRY;
170
171
172/**
173 * Diff state.
174 */
175typedef struct SCMDIFFSTATE
176{
177 size_t cDiffs;
178 const char *pszFilename;
179
180 PSCMSTREAM pLeft;
181 PSCMSTREAM pRight;
182
183 /** Whether to ignore end of line markers when diffing. */
184 bool fIgnoreEol;
185 /** Whether to ignore trailing whitespace. */
186 bool fIgnoreTrailingWhite;
187 /** Whether to ignore leading whitespace. */
188 bool fIgnoreLeadingWhite;
189 /** Whether to print special characters in human readable form or not. */
190 bool fSpecialChars;
191 /** The tab size. */
192 size_t cchTab;
193 /** Where to push the diff. */
194 PRTSTREAM pDiff;
195} SCMDIFFSTATE;
196/** Pointer to a diff state. */
197typedef SCMDIFFSTATE *PSCMDIFFSTATE;
198
199/**
200 * Source Code Massager Settings.
201 */
202typedef struct SCMSETTINGSBASE
203{
204 bool fConvertEol;
205 bool fConvertTabs;
206 bool fForceFinalEol;
207 bool fForceTrailingLine;
208 bool fStripTrailingBlanks;
209 bool fStripTrailingLines;
210 /** Only recurse into directories containing an .svn dir. */
211 bool fOnlySvnDirs;
212 /** */
213 unsigned cchTab;
214 /** Only consider files matcihng these patterns. This is only applied to the
215 * base names. */
216 char *pszFilterFiles;
217 /** Filter out files matching the following patterns. This is applied to base
218 * names as well as the aboslute paths. */
219 char *pszFilterOutFiles;
220 /** Filter out directories matching the following patterns. This is applied
221 * to base names as well as the aboslute paths. All absolute paths ends with a
222 * slash and dot ("/."). */
223 char *pszFilterOutDirs;
224} SCMSETTINGSBASE;
225/** Pointer to massager settings. */
226typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
227
228/**
229 * Option identifiers.
230 *
231 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
232 * clear. So, the option setting a flag (boolean) will have an even
233 * number and the one clearing it will have an odd number.
234 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
235 */
236typedef enum SCMOPT
237{
238 SCMOPT_CONVERT_EOL = 10000,
239 SCMOPT_NO_CONVERT_EOL,
240 SCMOPT_CONVERT_TABS,
241 SCMOPT_NO_CONVERT_TABS,
242 SCMOPT_FORCE_FINAL_EOL,
243 SCMOPT_NO_FORCE_FINAL_EOL,
244 SCMOPT_FORCE_TRAILING_LINE,
245 SCMOPT_NO_FORCE_TRAILING_LINE,
246 SCMOPT_STRIP_TRAILING_BLANKS,
247 SCMOPT_NO_STRIP_TRAILING_BLANKS,
248 SCMOPT_STRIP_TRAILING_LINES,
249 SCMOPT_NO_STRIP_TRAILING_LINES,
250 SCMOPT_ONLY_SVN_DIRS,
251 SCMOPT_NOT_ONLY_SVN_DIRS,
252 SCMOPT_TAB_SIZE,
253 SCMOPT_FILTER_OUT_DIRS,
254 SCMOPT_FILTER_FILES,
255 SCMOPT_FILTER_OUT_FILES,
256 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
257 //
258 SCMOPT_DIFF_IGNORE_EOL,
259 SCMOPT_DIFF_NO_IGNORE_EOL,
260 SCMOPT_DIFF_IGNORE_SPACE,
261 SCMOPT_DIFF_NO_IGNORE_SPACE,
262 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
263 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
264 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
265 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
266 SCMOPT_DIFF_SPECIAL_CHARS,
267 SCMOPT_DIFF_NO_SPECIAL_CHARS,
268 SCMOPT_END
269} SCMOPT;
270
271
272/**
273 * File/dir pattern + options.
274 */
275typedef struct SCMPATRNOPTPAIR
276{
277 char *pszPattern;
278 char *pszOptions;
279} SCMPATRNOPTPAIR;
280/** Pointer to a pattern + option pair. */
281typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
282
283
284/** Pointer to a settings set. */
285typedef struct SCMSETTINGS *PSCMSETTINGS;
286/**
287 * Settings set.
288 *
289 * This structure is constructed from the command line arguments or any
290 * .scm-settings file found in a directory we recurse into. When recusing in
291 * and out of a directory, we push and pop a settings set for it.
292 *
293 * The .scm-settings file has two kinds of setttings, first there are the
294 * unqualified base settings and then there are the settings which applies to a
295 * set of files or directories. The former are lines with command line options.
296 * For the latter, the options are preceeded by a string pattern and a colon.
297 * The pattern specifies which files (and/or directories) the options applies
298 * to.
299 *
300 * We parse the base options into the Base member and put the others into the
301 * paPairs array.
302 */
303typedef struct SCMSETTINGS
304{
305 /** Pointer to the setting file below us in the stack. */
306 PSCMSETTINGS pDown;
307 /** Pointer to the setting file above us in the stack. */
308 PSCMSETTINGS pUp;
309 /** File/dir patterns and their options. */
310 PSCMPATRNOPTPAIR paPairs;
311 /** The number of entires in paPairs. */
312 uint32_t cPairs;
313 /** The base settings that was read out of the file. */
314 SCMSETTINGSBASE Base;
315} SCMSETTINGS;
316/** Pointer to a const settings set. */
317typedef SCMSETTINGS const *PCSCMSETTINGS;
318
319
320/*******************************************************************************
321* Internal Functions *
322*******************************************************************************/
323static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
324static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
325static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
326static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
327static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
328static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
329static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
330static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
331static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
332
333
334/*******************************************************************************
335* Global Variables *
336*******************************************************************************/
337static const char g_szProgName[] = "scm";
338static const char *g_pszChangedSuff = "";
339static const char g_szTabSpaces[16+1] = " ";
340static bool g_fDryRun = true;
341static bool g_fDiffSpecialChars = true;
342static bool g_fDiffIgnoreEol = false;
343static bool g_fDiffIgnoreLeadingWS = false;
344static bool g_fDiffIgnoreTrailingWS = false;
345static int g_iVerbosity = 2;//99; //0;
346
347/** The global settings. */
348static SCMSETTINGSBASE const g_Defaults =
349{
350 /* .fConvertEol = */ true,
351 /* .fConvertTabs = */ true,
352 /* .fForceFinalEol = */ true,
353 /* .fForceTrailingLine = */ false,
354 /* .fStripTrailingBlanks = */ true,
355 /* .fStripTrailingLines = */ true,
356 /* .fOnlySvnDirs = */ false,
357 /* .cchTab = */ 8,
358 /* .pszFilterFiles = */ (char *)"",
359 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
360 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
361};
362
363/** Option definitions for the base settings. */
364static RTGETOPTDEF g_aScmOpts[] =
365{
366 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
367 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
368 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
369 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
370 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
371 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
372 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
373 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
374 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
375 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
376 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
377 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
378 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
379 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
380 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
381 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
382 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
383 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
384};
385
386/** Consider files matching the following patterns (base names only). */
387static const char *g_pszFileFilter = NULL;
388
389static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
390{
391 rewrite_Makefile_kup
392};
393
394static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
395{
396 rewrite_ForceNativeEol,
397 rewrite_StripTrailingBlanks,
398 rewrite_AdjustTrailingLines,
399 rewrite_Makefile_kmk
400};
401
402static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
403{
404 rewrite_ForceNativeEol,
405 rewrite_ExpandTabs,
406 rewrite_StripTrailingBlanks,
407 rewrite_AdjustTrailingLines,
408 rewrite_C_and_CPP
409};
410
411static PFNSCMREWRITER const g_aRewritersFor_RC[] =
412{
413 rewrite_ForceNativeEol,
414 rewrite_ExpandTabs,
415 rewrite_StripTrailingBlanks,
416 rewrite_AdjustTrailingLines
417};
418
419static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
420{
421 rewrite_ForceLF,
422 rewrite_ExpandTabs,
423 rewrite_StripTrailingBlanks
424};
425
426static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
427{
428 rewrite_ForceCRLF,
429 rewrite_ExpandTabs,
430 rewrite_StripTrailingBlanks
431};
432
433static SCMCFGENTRY const g_aConfigs[] =
434{
435 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
436 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk" },
437 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.h|*.cpp|*.hpp|*.C|*.CPP|*.cxx|*.cc" },
438 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
439 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
440 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
441};
442
443
444/* -=-=-=-=-=- memory streams -=-=-=-=-=- */
445
446
447/**
448 * Initializes the stream structure.
449 *
450 * @param pStream The stream structure.
451 * @param fWriteOrRead The value of the fWriteOrRead stream member.
452 */
453static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)
454{
455 pStream->pch = NULL;
456 pStream->off = 0;
457 pStream->cb = 0;
458 pStream->cbAllocated = 0;
459
460 pStream->paLines = NULL;
461 pStream->iLine = 0;
462 pStream->cLines = 0;
463 pStream->cLinesAllocated = 0;
464
465 pStream->fWriteOrRead = fWriteOrRead;
466 pStream->fFileMemory = false;
467 pStream->fFullyLineated = false;
468
469 pStream->rc = VINF_SUCCESS;
470}
471
472/**
473 * Initialize an input stream.
474 *
475 * @returns IPRT status code.
476 * @param pStream The stream to initialize.
477 * @param pszFilename The file to take the stream content from.
478 */
479int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)
480{
481 scmStreamInitInternal(pStream, false /*fWriteOrRead*/);
482
483 void *pvFile;
484 size_t cbFile;
485 int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);
486 if (RT_SUCCESS(rc))
487 {
488 pStream->pch = (char *)pvFile;
489 pStream->cb = cbFile;
490 pStream->cbAllocated = cbFile;
491 pStream->fFileMemory = true;
492 }
493 return rc;
494}
495
496/**
497 * Initialize an output stream.
498 *
499 * @returns IPRT status code
500 * @param pStream The stream to initialize.
501 * @param pRelatedStream Pointer to a related stream. NULL is fine.
502 */
503int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)
504{
505 scmStreamInitInternal(pStream, true /*fWriteOrRead*/);
506
507 /* allocate stuff */
508 size_t cbEstimate = pRelatedStream
509 ? pRelatedStream->cb + pRelatedStream->cb / 10
510 : _64K;
511 cbEstimate = RT_ALIGN(cbEstimate, _4K);
512 pStream->pch = (char *)RTMemAlloc(cbEstimate);
513 if (pStream->pch)
514 {
515 size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated
516 ? pRelatedStream->cLines + pRelatedStream->cLines / 10
517 : cbEstimate / 24;
518 cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);
519 pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));
520 if (pStream->paLines)
521 {
522 pStream->paLines[0].off = 0;
523 pStream->paLines[0].cch = 0;
524 pStream->paLines[0].enmEol = SCMEOL_NONE;
525 pStream->cbAllocated = cbEstimate;
526 pStream->cLinesAllocated = cLinesEstimate;
527 return VINF_SUCCESS;
528 }
529
530 RTMemFree(pStream->pch);
531 pStream->pch = NULL;
532 }
533 return pStream->rc = VERR_NO_MEMORY;
534}
535
536/**
537 * Frees the resources associated with the stream.
538 *
539 * Nothing is happens to whatever the stream was initialized from or dumped to.
540 *
541 * @param pStream The stream to delete.
542 */
543void ScmStreamDelete(PSCMSTREAM pStream)
544{
545 if (pStream->pch)
546 {
547 if (pStream->fFileMemory)
548 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
549 else
550 RTMemFree(pStream->pch);
551 pStream->pch = NULL;
552 }
553 pStream->cbAllocated = 0;
554
555 if (pStream->paLines)
556 {
557 RTMemFree(pStream->paLines);
558 pStream->paLines = NULL;
559 }
560 pStream->cLinesAllocated = 0;
561}
562
563/**
564 * Get the stream status code.
565 *
566 * @returns IPRT status code.
567 * @param pStream The stream.
568 */
569int ScmStreamGetStatus(PCSCMSTREAM pStream)
570{
571 return pStream->rc;
572}
573
574/**
575 * Grows the buffer of a write stream.
576 *
577 * @returns IPRT status code.
578 * @param pStream The stream. Must be in write mode.
579 * @param cbAppending The minimum number of bytes to grow the buffer
580 * with.
581 */
582static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)
583{
584 size_t cbAllocated = pStream->cbAllocated;
585 cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);
586 cbAllocated = RT_ALIGN(cbAllocated, 0x1000);
587 void *pvNew;
588 if (!pStream->fFileMemory)
589 {
590 pvNew = RTMemRealloc(pStream->pch, cbAllocated);
591 if (!pvNew)
592 return pStream->rc = VERR_NO_MEMORY;
593 }
594 else
595 {
596 pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);
597 if (!pvNew)
598 return pStream->rc = VERR_NO_MEMORY;
599 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
600 pStream->fFileMemory = false;
601 }
602 pStream->pch = (char *)pvNew;
603 pStream->cbAllocated = cbAllocated;
604
605 return VINF_SUCCESS;
606}
607
608/**
609 * Grows the line array of a stream.
610 *
611 * @returns IPRT status code.
612 * @param pStream The stream.
613 * @param iMinLine Minimum line number.
614 */
615static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)
616{
617 size_t cLinesAllocated = pStream->cLinesAllocated;
618 cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);
619 cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);
620 void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));
621 if (!pvNew)
622 return pStream->rc = VERR_NO_MEMORY;
623
624 pStream->paLines = (PSCMSTREAMLINE)pvNew;
625 pStream->cLinesAllocated = cLinesAllocated;
626 return VINF_SUCCESS;
627}
628
629/**
630 * Rewinds the stream and sets the mode to read.
631 *
632 * @param pStream The stream.
633 */
634void ScmStreamRewindForReading(PSCMSTREAM pStream)
635{
636 pStream->off = 0;
637 pStream->iLine = 0;
638 pStream->fWriteOrRead = false;
639 pStream->rc = VINF_SUCCESS;
640}
641
642/**
643 * Rewinds the stream and sets the mode to write.
644 *
645 * @param pStream The stream.
646 */
647void ScmStreamRewindForWriting(PSCMSTREAM pStream)
648{
649 pStream->off = 0;
650 pStream->iLine = 0;
651 pStream->cLines = 0;
652 pStream->fWriteOrRead = true;
653 pStream->fFullyLineated = true;
654 pStream->rc = VINF_SUCCESS;
655}
656
657/**
658 * Checks if it's a text stream.
659 *
660 * Not 100% proof.
661 *
662 * @returns true if it probably is a text file, false if not.
663 * @param pStream The stream. Write or read, doesn't matter.
664 */
665bool ScmStreamIsText(PSCMSTREAM pStream)
666{
667 if (memchr(pStream->pch, '\0', pStream->cb))
668 return false;
669 if (!pStream->cb)
670 return false;
671 return true;
672}
673
674/**
675 * Performs an integrity check of the stream.
676 *
677 * @returns IPRT status code.
678 * @param pStream The stream.
679 */
680int ScmStreamCheckItegrity(PSCMSTREAM pStream)
681{
682 /*
683 * Perform sanity checks.
684 */
685 size_t const cbFile = pStream->cb;
686 for (size_t iLine = 0; iLine < pStream->cLines; iLine++)
687 {
688 size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;
689 AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);
690 switch (pStream->paLines[iLine].enmEol)
691 {
692 case SCMEOL_LF:
693 AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);
694 break;
695 case SCMEOL_CRLF:
696 AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);
697 AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);
698 break;
699 case SCMEOL_NONE:
700 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);
701 break;
702 default:
703 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);
704 }
705 }
706 return VINF_SUCCESS;
707}
708
709/**
710 * Writes the stream to a file.
711 *
712 * @returns IPRT status code
713 * @param pStream The stream.
714 * @param pszFilenameFmt The filename format string.
715 * @param ... Format arguments.
716 */
717int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)
718{
719 int rc;
720
721#ifdef RT_STRICT
722 /*
723 * Check that what we're going to write makes sense first.
724 */
725 rc = ScmStreamCheckItegrity(pStream);
726 if (RT_FAILURE(rc))
727 return rc;
728#endif
729
730 /*
731 * Do the actual writing.
732 */
733 RTFILE hFile;
734 va_list va;
735 va_start(va, pszFilenameFmt);
736 rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);
737 if (RT_SUCCESS(rc))
738 {
739 rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);
740 RTFileClose(hFile);
741 }
742 return rc;
743}
744
745/**
746 * Worker for ScmStreamGetLine that builds the line number index while parsing
747 * the stream.
748 *
749 * @returns Same as SCMStreamGetLine.
750 * @param pStream The stream. Must be in read mode.
751 * @param pcchLine Where to return the line length.
752 * @param penmEol Where to return the kind of end of line marker.
753 */
754static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
755{
756 AssertReturn(!pStream->fWriteOrRead, NULL);
757 if (RT_FAILURE(pStream->rc))
758 return NULL;
759
760 size_t off = pStream->off;
761 size_t cb = pStream->cb;
762 if (RT_UNLIKELY(off >= cb))
763 {
764 pStream->fFullyLineated = true;
765 return NULL;
766 }
767
768 size_t iLine = pStream->iLine;
769 if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))
770 {
771 int rc = scmStreamGrowLines(pStream, iLine);
772 if (RT_FAILURE(rc))
773 return NULL;
774 }
775 pStream->paLines[iLine].off = off;
776
777 cb -= off;
778 const char *pchRet = &pStream->pch[off];
779 const char *pch = (const char *)memchr(pchRet, '\n', cb);
780 if (RT_LIKELY(pch))
781 {
782 cb = pch - pchRet;
783 pStream->off = off + cb + 1;
784 if ( cb < 1
785 || pch[-1] != '\r')
786 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;
787 else
788 {
789 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;
790 cb--;
791 }
792 }
793 else
794 {
795 pStream->off = off + cb;
796 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;
797 }
798 *pcchLine = cb;
799 pStream->paLines[iLine].cch = cb;
800 pStream->cLines = pStream->iLine = ++iLine;
801
802 return pchRet;
803}
804
805/**
806 * Internal worker that lineates a stream.
807 *
808 * @returns IPRT status code.
809 * @param pStream The stream. Caller must check that it is in
810 * read mode.
811 */
812static int scmStreamLineate(PSCMSTREAM pStream)
813{
814 /* Save the stream position. */
815 size_t const offSaved = pStream->off;
816 size_t const iLineSaved = pStream->iLine;
817
818 /* Get each line. */
819 size_t cchLine;
820 SCMEOL enmEol;
821 while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))
822 /* nothing */;
823 Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);
824
825 /* Restore the position */
826 pStream->off = offSaved;
827 pStream->iLine = iLineSaved;
828
829 return pStream->rc;
830}
831
832/**
833 * Get the current stream position as a line number.
834 *
835 * @returns The current line (0-based).
836 * @param pStream The stream.
837 */
838size_t ScmStreamTellLine(PSCMSTREAM pStream)
839{
840 return pStream->iLine;
841}
842
843/**
844 * Gets the number of lines in the stream.
845 *
846 * @returns The number of lines.
847 * @param pStream The stream.
848 */
849size_t ScmStreamCountLines(PSCMSTREAM pStream)
850{
851 if (!pStream->fFullyLineated)
852 scmStreamLineate(pStream);
853 return pStream->cLines;
854}
855
856/**
857 * Seeks to a given line in the tream.
858 *
859 * @returns IPRT status code.
860 *
861 * @param pStream The stream. Must be in read mode.
862 * @param iLine The line to seek to. If this is beyond the end
863 * of the stream, the position is set to the end.
864 */
865int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)
866{
867 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
868 if (RT_FAILURE(pStream->rc))
869 return pStream->rc;
870
871 /* Must be fully lineated of course. */
872 if (RT_UNLIKELY(!pStream->fFullyLineated))
873 {
874 int rc = scmStreamLineate(pStream);
875 if (RT_FAILURE(rc))
876 return rc;
877 }
878
879 /* Ok, do the job. */
880 if (iLine < pStream->cLines)
881 {
882 pStream->off = pStream->paLines[iLine].off;
883 pStream->iLine = iLine;
884 }
885 else
886 {
887 pStream->off = pStream->cb;
888 pStream->iLine = pStream->cLines;
889 }
890 return VINF_SUCCESS;
891}
892
893/**
894 * Get a numbered line from the stream (changes the position).
895 *
896 * A line is always delimited by a LF character or the end of the stream. The
897 * delimiter is not included in returned line length, but instead returned via
898 * the @a penmEol indicator.
899 *
900 * @returns Pointer to the first character in the line, not NULL terminated.
901 * NULL if the end of the stream has been reached or some problem
902 * occured.
903 *
904 * @param pStream The stream. Must be in read mode.
905 * @param iLine The line to get (0-based).
906 * @param pcchLine The length.
907 * @param penmEol Where to return the end of line type indicator.
908 */
909static const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
910{
911 AssertReturn(!pStream->fWriteOrRead, NULL);
912 if (RT_FAILURE(pStream->rc))
913 return NULL;
914
915 /* Make sure it's fully lineated so we can use the index. */
916 if (RT_UNLIKELY(!pStream->fFullyLineated))
917 {
918 int rc = scmStreamLineate(pStream);
919 if (RT_FAILURE(rc))
920 return NULL;
921 }
922
923 /* End of stream? */
924 if (RT_UNLIKELY(iLine >= pStream->cLines))
925 {
926 pStream->off = pStream->cb;
927 pStream->iLine = pStream->cLines;
928 return NULL;
929 }
930
931 /* Get the data. */
932 const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];
933 *pcchLine = pStream->paLines[iLine].cch;
934 *penmEol = pStream->paLines[iLine].enmEol;
935
936 /* update the stream position. */
937 pStream->off = pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;
938 pStream->iLine = iLine + 1;
939
940 return pchRet;
941}
942
943/**
944 * Get a line from the stream.
945 *
946 * A line is always delimited by a LF character or the end of the stream. The
947 * delimiter is not included in returned line length, but instead returned via
948 * the @a penmEol indicator.
949 *
950 * @returns Pointer to the first character in the line, not NULL terminated.
951 * NULL if the end of the stream has been reached or some problem
952 * occured.
953 *
954 * @param pStream The stream. Must be in read mode.
955 * @param pcchLine The length.
956 * @param penmEol Where to return the end of line type indicator.
957 */
958static const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
959{
960 if (!pStream->fFullyLineated)
961 return scmStreamGetLineInternal(pStream, pcchLine, penmEol);
962 return ScmStreamGetLineByNo(pStream, pStream->iLine, pcchLine, penmEol);
963}
964
965/**
966 * Checks if the given line is empty or full of white space.
967 *
968 * @returns true if white space only, false if not (or if non-existant).
969 * @param pStream The stream. Must be in read mode.
970 * @param iLine The line in question.
971 */
972static bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
973{
974 SCMEOL enmEol;
975 size_t cchLine;
976 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
977 if (!pchLine)
978 return false;
979 while (cchLine && RT_C_IS_SPACE(*pchLine))
980 pchLine++, cchLine--;
981 return cchLine == 0;
982}
983
984/**
985 * Try figure out the end of line style of the give stream.
986 *
987 * @returns Most likely end of line style.
988 * @param pStream The stream.
989 */
990SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)
991{
992 SCMEOL enmEol;
993 if (pStream->cLines > 0)
994 enmEol = pStream->paLines[0].enmEol;
995 else if (pStream->cb == 0)
996 enmEol = SCMEOL_NONE;
997 else
998 {
999 const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);
1000 if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')
1001 enmEol = SCMEOL_CRLF;
1002 else
1003 enmEol = SCMEOL_LF;
1004 }
1005
1006 if (enmEol == SCMEOL_NONE)
1007#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1008 enmEol = SCMEOL_CRLF;
1009#else
1010 enmEol = SCMEOL_LF;
1011#endif
1012 return enmEol;
1013}
1014
1015/**
1016 * Get the end of line indicator type for a line.
1017 *
1018 * @returns The EOL indicator. If the line isn't found, the default EOL
1019 * indicator is return.
1020 * @param pStream The stream.
1021 * @param iLine The line (0-base).
1022 */
1023SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)
1024{
1025 SCMEOL enmEol;
1026 if (iLine < pStream->cLines)
1027 enmEol = pStream->paLines[iLine].enmEol;
1028 else
1029#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1030 enmEol = SCMEOL_CRLF;
1031#else
1032 enmEol = SCMEOL_LF;
1033#endif
1034 return enmEol;
1035}
1036
1037/**
1038 * Appends a line to the stream.
1039 *
1040 * @returns IPRT status code.
1041 * @param pStream The stream. Must be in write mode.
1042 * @param pchLine Pointer to the line.
1043 * @param cchLine Line length.
1044 * @param enmEol Which end of line indicator to use.
1045 */
1046int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)
1047{
1048 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
1049 if (RT_FAILURE(pStream->rc))
1050 return pStream->rc;
1051
1052 /*
1053 * Make sure the previous line has a new-line indicator.
1054 */
1055 size_t off = pStream->off;
1056 size_t iLine = pStream->iLine;
1057 if (RT_UNLIKELY( iLine != 0
1058 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
1059 {
1060 AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);
1061 SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);
1062 if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))
1063 {
1064 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);
1065 if (RT_FAILURE(rc))
1066 return rc;
1067 }
1068 if (enmEol2 == SCMEOL_LF)
1069 pStream->pch[off++] = '\n';
1070 else
1071 {
1072 pStream->pch[off++] = '\r';
1073 pStream->pch[off++] = '\n';
1074 }
1075 pStream->paLines[iLine - 1].enmEol = enmEol2;
1076 pStream->paLines[iLine].off = off;
1077 pStream->off = off;
1078 pStream->cb = off;
1079 }
1080
1081 /*
1082 * Ensure we've got sufficient buffer space.
1083 */
1084 if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))
1085 {
1086 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);
1087 if (RT_FAILURE(rc))
1088 return rc;
1089 }
1090
1091 /*
1092 * Add a line record.
1093 */
1094 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
1095 {
1096 int rc = scmStreamGrowLines(pStream, iLine);
1097 if (RT_FAILURE(rc))
1098 return rc;
1099 }
1100
1101 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine;
1102 pStream->paLines[iLine].enmEol = enmEol;
1103
1104 iLine++;
1105 pStream->cLines = iLine;
1106 pStream->iLine = iLine;
1107
1108 /*
1109 * Copy the line
1110 */
1111 memcpy(&pStream->pch[off], pchLine, cchLine);
1112 off += cchLine;
1113 if (enmEol == SCMEOL_LF)
1114 pStream->pch[off++] = '\n';
1115 else if (enmEol == SCMEOL_CRLF)
1116 {
1117 pStream->pch[off++] = '\r';
1118 pStream->pch[off++] = '\n';
1119 }
1120 pStream->off = off;
1121 pStream->cb = off;
1122
1123 /*
1124 * Start a new line.
1125 */
1126 pStream->paLines[iLine].off = off;
1127 pStream->paLines[iLine].cch = 0;
1128 pStream->paLines[iLine].enmEol = SCMEOL_NONE;
1129
1130 return VINF_SUCCESS;
1131}
1132
1133/**
1134 * Writes to the stream.
1135 *
1136 * @returns IPRT status code
1137 * @param pStream The stream. Must be in write mode.
1138 * @param pchBuf What to write.
1139 * @param cchBuf How much to write.
1140 */
1141int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)
1142{
1143 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
1144 if (RT_FAILURE(pStream->rc))
1145 return pStream->rc;
1146
1147 /*
1148 * Ensure we've got sufficient buffer space.
1149 */
1150 size_t off = pStream->off;
1151 if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))
1152 {
1153 int rc = scmStreamGrowBuffer(pStream, cchBuf);
1154 if (RT_FAILURE(rc))
1155 return rc;
1156 }
1157
1158 /*
1159 * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.
1160 */
1161 size_t iLine = pStream->iLine;
1162 if (RT_UNLIKELY( iLine > 0
1163 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
1164 {
1165 iLine--;
1166 pStream->cLines = iLine;
1167 pStream->iLine = iLine;
1168 }
1169
1170 /*
1171 * Deal with lines.
1172 */
1173 const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);
1174 if (!pchLF)
1175 pStream->paLines[iLine].cch += cchBuf;
1176 else
1177 {
1178 const char *pchLine = pchBuf;
1179 for (;;)
1180 {
1181 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
1182 {
1183 int rc = scmStreamGrowLines(pStream, iLine);
1184 if (RT_FAILURE(rc))
1185 {
1186 iLine = pStream->iLine;
1187 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off;
1188 pStream->paLines[iLine].enmEol = SCMEOL_NONE;
1189 return rc;
1190 }
1191 }
1192
1193 size_t cchLine = pchLF - pchLine;
1194 if ( cchLine
1195 ? pchLF[-1] != '\r'
1196 : !pStream->paLines[iLine].cch
1197 || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')
1198 pStream->paLines[iLine].enmEol = SCMEOL_LF;
1199 else
1200 {
1201 pStream->paLines[iLine].enmEol = SCMEOL_CRLF;
1202 cchLine--;
1203 }
1204 pStream->paLines[iLine].cch += cchLine;
1205
1206 iLine++;
1207 size_t offBuf = pchLF + 1 - pchBuf;
1208 pStream->paLines[iLine].off = off + offBuf;
1209 pStream->paLines[iLine].cch = 0;
1210 pStream->paLines[iLine].enmEol = SCMEOL_NONE;
1211
1212 size_t cchLeft = cchBuf - offBuf;
1213 pchLF = (const char *)memchr(pchLF + 1, '\n', cchLeft);
1214 if (!pchLF)
1215 {
1216 pStream->paLines[iLine].cch = cchLeft;
1217 break;
1218 }
1219 }
1220
1221 pStream->iLine = iLine;
1222 pStream->cLines = iLine;
1223 }
1224
1225 /*
1226 * Copy the data and update position and size.
1227 */
1228 memcpy(&pStream->pch[off], pchBuf, cchBuf);
1229 off += cchBuf;
1230 pStream->off = off;
1231 pStream->cb = off;
1232
1233 return VINF_SUCCESS;
1234}
1235
1236/**
1237 * Write a character to the stream.
1238 *
1239 * @returns IPRT status code
1240 * @param pStream The stream. Must be in write mode.
1241 * @param pchBuf What to write.
1242 * @param cchBuf How much to write.
1243 */
1244int ScmStreamPutCh(PSCMSTREAM pStream, char ch)
1245{
1246 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
1247 if (RT_FAILURE(pStream->rc))
1248 return pStream->rc;
1249
1250 /*
1251 * Only deal with the simple cases here, use ScmStreamWrite for the
1252 * annyoing stuff.
1253 */
1254 size_t off = pStream->off;
1255 if ( ch == '\n'
1256 || RT_UNLIKELY(off + 1 > pStream->cbAllocated))
1257 return ScmStreamWrite(pStream, &ch, 1);
1258
1259 /*
1260 * Just append it.
1261 */
1262 pStream->pch[off] = ch;
1263 pStream->off = off + 1;
1264 pStream->paLines[pStream->iLine].cch++;
1265
1266 return VINF_SUCCESS;
1267}
1268
1269/**
1270 * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.
1271 *
1272 * The stream positions will be used and changed in both streams.
1273 *
1274 * @returns IPRT status code.
1275 * @param pDst The destionation stream. Must be in write mode.
1276 * @param cLines The number of lines. (0 is accepted.)
1277 * @param pSrc The source stream. Must be in read mode.
1278 */
1279int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)
1280{
1281 AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);
1282 if (RT_FAILURE(pDst->rc))
1283 return pDst->rc;
1284
1285 AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);
1286 if (RT_FAILURE(pSrc->rc))
1287 return pSrc->rc;
1288
1289 while (cLines-- > 0)
1290 {
1291 SCMEOL enmEol;
1292 size_t cchLine;
1293 const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);
1294 if (!pchLine)
1295 return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF);
1296
1297 int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);
1298 if (RT_FAILURE(rc))
1299 return rc;
1300 }
1301
1302 return VINF_SUCCESS;
1303}
1304
1305/* -=-=-=-=-=- diff -=-=-=-=-=- */
1306
1307
1308/**
1309 * Prints a range of lines with a prefix.
1310 *
1311 * @param pState The diff state.
1312 * @param chPrefix The prefix.
1313 * @param pStream The stream to get the lines from.
1314 * @param iLine The first line.
1315 * @param cLines The number of lines.
1316 */
1317static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
1318{
1319 while (cLines-- > 0)
1320 {
1321 SCMEOL enmEol;
1322 size_t cchLine;
1323 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
1324
1325 RTStrmPutCh(pState->pDiff, chPrefix);
1326 if (pchLine && cchLine)
1327 {
1328 if (!pState->fSpecialChars)
1329 RTStrmWrite(pState->pDiff, pchLine, cchLine);
1330 else
1331 {
1332 size_t offVir = 0;
1333 const char *pchStart = pchLine;
1334 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
1335 while (pchTab)
1336 {
1337 RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
1338 offVir += pchTab - pchStart;
1339
1340 size_t cchTab = pState->cchTab - offVir % pState->cchTab;
1341 switch (cchTab)
1342 {
1343 case 1: RTStrmPutStr(pState->pDiff, "."); break;
1344 case 2: RTStrmPutStr(pState->pDiff, ".."); break;
1345 case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
1346 case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
1347 case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
1348 default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
1349 }
1350 offVir += cchTab;
1351
1352 /* next */
1353 pchStart = pchTab + 1;
1354 pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
1355 }
1356 size_t cchLeft = cchLine - (pchStart - pchLine);
1357 if (cchLeft)
1358 RTStrmWrite(pState->pDiff, pchStart, cchLeft);
1359 }
1360 }
1361
1362 if (!pState->fSpecialChars)
1363 RTStrmPutCh(pState->pDiff, '\n');
1364 else if (enmEol == SCMEOL_LF)
1365 RTStrmPutStr(pState->pDiff, "[LF]\n");
1366 else if (enmEol == SCMEOL_CRLF)
1367 RTStrmPutStr(pState->pDiff, "[CRLF]\n");
1368 else
1369 RTStrmPutStr(pState->pDiff, "[NONE]\n");
1370
1371 iLine++;
1372 }
1373}
1374
1375
1376/**
1377 * Reports a difference and propells the streams to the lines following the
1378 * resync.
1379 *
1380 *
1381 * @returns New pState->cDiff value (just to return something).
1382 * @param pState The diff state. The cDiffs member will be
1383 * incremented.
1384 * @param cMatches The resync length.
1385 * @param iLeft Where the difference starts on the left side.
1386 * @param cLeft How long it is on this side. ~(size_t)0 is used
1387 * to indicate that it goes all the way to the end.
1388 * @param iRight Where the difference starts on the right side.
1389 * @param cRight How long it is.
1390 */
1391static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
1392 size_t iLeft, size_t cLeft,
1393 size_t iRight, size_t cRight)
1394{
1395 /*
1396 * Adjust the input.
1397 */
1398 if (cLeft == ~(size_t)0)
1399 {
1400 size_t c = ScmStreamCountLines(pState->pLeft);
1401 if (c >= iLeft)
1402 cLeft = c - iLeft;
1403 else
1404 {
1405 iLeft = c;
1406 cLeft = 0;
1407 }
1408 }
1409
1410 if (cRight == ~(size_t)0)
1411 {
1412 size_t c = ScmStreamCountLines(pState->pRight);
1413 if (c >= iRight)
1414 cRight = c - iRight;
1415 else
1416 {
1417 iRight = c;
1418 cRight = 0;
1419 }
1420 }
1421
1422 /*
1423 * Print header if it's the first difference
1424 */
1425 if (!pState->cDiffs)
1426 RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
1427
1428 /*
1429 * Emit the change description.
1430 */
1431 char ch = cLeft == 0
1432 ? 'a'
1433 : cRight == 0
1434 ? 'd'
1435 : 'c';
1436 if (cLeft > 1 && cRight > 1)
1437 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
1438 else if (cLeft > 1)
1439 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1);
1440 else if (cRight > 1)
1441 RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight);
1442 else
1443 RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1);
1444
1445 /*
1446 * And the lines.
1447 */
1448 if (cLeft)
1449 scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
1450 if (cLeft && cRight)
1451 RTStrmPrintf(pState->pDiff, "---\n");
1452 if (cRight)
1453 scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
1454
1455 /*
1456 * Reposition the streams (safely ignores return value).
1457 */
1458 ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches);
1459 ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
1460
1461 pState->cDiffs++;
1462 return pState->cDiffs;
1463}
1464
1465/**
1466 * Helper for scmDiffCompare that takes care of trailing spaces and stuff
1467 * like that.
1468 */
1469static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
1470 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
1471 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
1472{
1473 if (pState->fIgnoreTrailingWhite)
1474 {
1475 while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
1476 cchLeft--;
1477 while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
1478 cchRight--;
1479 }
1480
1481 if (pState->fIgnoreLeadingWhite)
1482 {
1483 while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
1484 pchLeft++, cchLeft--;
1485 while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
1486 pchRight++, cchRight--;
1487 }
1488
1489 if ( cchLeft != cchRight
1490 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
1491 || memcmp(pchLeft, pchRight, cchLeft))
1492 return false;
1493 return true;
1494}
1495
1496/**
1497 * Compare two lines.
1498 *
1499 * @returns true if the are equal, false if not.
1500 */
1501DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
1502 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
1503 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
1504{
1505 if ( cchLeft != cchRight
1506 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
1507 || memcmp(pchLeft, pchRight, cchLeft))
1508 {
1509 if ( pState->fIgnoreTrailingWhite
1510 || pState->fIgnoreTrailingWhite)
1511 return scmDiffCompareSlow(pState,
1512 pchLeft, cchLeft, enmEolLeft,
1513 pchRight, cchRight, enmEolRight);
1514 return false;
1515 }
1516 return true;
1517}
1518
1519/**
1520 * Compares two sets of lines from the two files.
1521 *
1522 * @returns true if they matches, false if they don't.
1523 * @param pState The diff state.
1524 * @param iLeft Where to start in the left stream.
1525 * @param iRight Where to start in the right stream.
1526 * @param cLines How many lines to compare.
1527 */
1528static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
1529{
1530 for (size_t iLine = 0; iLine < cLines; iLine++)
1531 {
1532 SCMEOL enmEolLeft;
1533 size_t cchLeft;
1534 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft);
1535
1536 SCMEOL enmEolRight;
1537 size_t cchRight;
1538 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
1539
1540 if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
1541 return false;
1542 }
1543 return true;
1544}
1545
1546
1547/**
1548 * Resynchronize the two streams and reports the difference.
1549 *
1550 * Upon return, the streams will be positioned after the block of @a cMatches
1551 * lines where it resynchronized them.
1552 *
1553 * @returns pState->cDiffs (just so we can use it in a return statement).
1554 * @param pState The state.
1555 * @param cMatches The number of lines that needs to match for the
1556 * stream to be considered synchronized again.
1557 */
1558static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
1559{
1560 size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1;
1561 size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
1562 Assert(cMatches > 0);
1563
1564 /*
1565 * Compare each new line from each of the streams will all the preceding
1566 * ones, including iStartLeft/Right.
1567 */
1568 for (size_t iRange = 1; ; iRange++)
1569 {
1570 /*
1571 * Get the next line in the left stream and compare it against all the
1572 * preceding lines on the right side.
1573 */
1574 SCMEOL enmEol;
1575 size_t cchLine;
1576 const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
1577 if (!pchLine)
1578 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
1579
1580 for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
1581 {
1582 SCMEOL enmEolRight;
1583 size_t cchRight;
1584 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
1585 &cchRight, &enmEolRight);
1586 if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
1587 && scmDiffCompareLines(pState,
1588 iStartLeft + iRange + 1 - cMatches,
1589 iStartRight + iRight + 1 - cMatches,
1590 cMatches - 1)
1591 )
1592 return scmDiffReport(pState, cMatches,
1593 iStartLeft, iRange + 1 - cMatches,
1594 iStartRight, iRight + 1 - cMatches);
1595 }
1596
1597 /*
1598 * Get the next line in the right stream and compare it against all the
1599 * lines on the right side.
1600 */
1601 pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
1602 if (!pchLine)
1603 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
1604
1605 for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
1606 {
1607 SCMEOL enmEolLeft;
1608 size_t cchLeft;
1609 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
1610 &cchLeft, &enmEolLeft);
1611 if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
1612 && scmDiffCompareLines(pState,
1613 iStartLeft + iLeft + 1 - cMatches,
1614 iStartRight + iRange + 1 - cMatches,
1615 cMatches - 1)
1616 )
1617 return scmDiffReport(pState, cMatches,
1618 iStartLeft, iLeft + 1 - cMatches,
1619 iStartRight, iRange + 1 - cMatches);
1620 }
1621 }
1622}
1623
1624/**
1625 * Creates a diff of the changes between the streams @a pLeft and @a pRight.
1626 *
1627 * This currently only implements the simplest diff format, so no contexts.
1628 *
1629 * Also, note that we won't detect differences in the final newline of the
1630 * streams.
1631 *
1632 * @returns The number of differences.
1633 * @param pszFilename The filename.
1634 * @param pLeft The left side stream.
1635 * @param pRight The right side stream.
1636 * @param fIgnoreEol Whether to ignore end of line markers.
1637 * @param fIgnoreLeadingWhite Set if leading white space should be ignored.
1638 * @param fIgnoreTrailingWhite Set if trailing white space should be ignored.
1639 * @param fSpecialChars Whether to print special chars in a human
1640 * readable form or not.
1641 * @param cchTab The tab size.
1642 * @param pDiff Where to write the diff.
1643 */
1644size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
1645 bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
1646 size_t cchTab, PRTSTREAM pDiff)
1647{
1648#ifdef RT_STRICT
1649 ScmStreamCheckItegrity(pLeft);
1650 ScmStreamCheckItegrity(pRight);
1651#endif
1652
1653 /*
1654 * Set up the diff state.
1655 */
1656 SCMDIFFSTATE State;
1657 State.cDiffs = 0;
1658 State.pszFilename = pszFilename;
1659 State.pLeft = pLeft;
1660 State.pRight = pRight;
1661 State.fIgnoreEol = fIgnoreEol;
1662 State.fIgnoreLeadingWhite = fIgnoreLeadingWhite;
1663 State.fIgnoreTrailingWhite = fIgnoreTrailingWhite;
1664 State.fSpecialChars = fSpecialChars;
1665 State.cchTab = cchTab;
1666 State.pDiff = pDiff;
1667
1668 /*
1669 * Compare them line by line.
1670 */
1671 ScmStreamRewindForReading(pLeft);
1672 ScmStreamRewindForReading(pRight);
1673 const char *pchLeft;
1674 const char *pchRight;
1675
1676 for (;;)
1677 {
1678 SCMEOL enmEolLeft;
1679 size_t cchLeft;
1680 pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft);
1681
1682 SCMEOL enmEolRight;
1683 size_t cchRight;
1684 pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
1685 if (!pchLeft || !pchRight)
1686 break;
1687
1688 if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
1689 scmDiffSynchronize(&State, 3);
1690 }
1691
1692 /*
1693 * Deal with any remaining differences.
1694 */
1695 if (pchLeft)
1696 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
1697 else if (pchRight)
1698 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
1699
1700 /*
1701 * Report any errors.
1702 */
1703 if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
1704 RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
1705 if (RT_FAILURE(ScmStreamGetStatus(pRight)))
1706 RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
1707
1708 return State.cDiffs;
1709}
1710
1711
1712
1713/* -=-=-=-=-=- settings -=-=-=-=-=- */
1714
1715/**
1716 * Init a settings structure with settings from @a pSrc.
1717 *
1718 * @returns IPRT status code
1719 * @param pSettings The settings.
1720 * @param pSrc The source settings.
1721 */
1722static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
1723{
1724 *pSettings = *pSrc;
1725
1726 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
1727 if (RT_SUCCESS(rc))
1728 {
1729 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
1730 if (RT_SUCCESS(rc))
1731 {
1732 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
1733 if (RT_SUCCESS(rc))
1734 return VINF_SUCCESS;
1735
1736 RTStrFree(pSettings->pszFilterOutFiles);
1737 }
1738 RTStrFree(pSettings->pszFilterFiles);
1739 }
1740
1741 pSettings->pszFilterFiles = NULL;
1742 pSettings->pszFilterOutFiles = NULL;
1743 pSettings->pszFilterOutDirs = NULL;
1744 return rc;
1745}
1746
1747/**
1748 * Init a settings structure.
1749 *
1750 * @returns IPRT status code
1751 * @param pSettings The settings.
1752 */
1753static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
1754{
1755 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
1756}
1757
1758/**
1759 * Deletes the settings, i.e. free any dynamically allocated content.
1760 *
1761 * @param pSettings The settings.
1762 */
1763static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
1764{
1765 if (pSettings)
1766 {
1767 Assert(pSettings->cchTab != ~(unsigned)0);
1768 pSettings->cchTab = ~(unsigned)0;
1769
1770 RTStrFree(pSettings->pszFilterFiles);
1771 pSettings->pszFilterFiles = NULL;
1772
1773 RTStrFree(pSettings->pszFilterOutFiles);
1774 pSettings->pszFilterOutFiles = NULL;
1775
1776 RTStrFree(pSettings->pszFilterOutDirs);
1777 pSettings->pszFilterOutDirs = NULL;
1778 }
1779}
1780
1781
1782/**
1783 * Processes a RTGetOpt result.
1784 *
1785 * @retval VINF_SUCCESS if handled.
1786 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
1787 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
1788 *
1789 * @param pSettings The settings to change.
1790 * @param rc The RTGetOpt return value.
1791 * @param pValueUnion The RTGetOpt value union.
1792 */
1793static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
1794{
1795 switch (rc)
1796 {
1797 case SCMOPT_CONVERT_EOL:
1798 pSettings->fConvertEol = true;
1799 return VINF_SUCCESS;
1800 case SCMOPT_NO_CONVERT_EOL:
1801 pSettings->fConvertEol = false;
1802 return VINF_SUCCESS;
1803
1804 case SCMOPT_CONVERT_TABS:
1805 pSettings->fConvertTabs = true;
1806 return VINF_SUCCESS;
1807 case SCMOPT_NO_CONVERT_TABS:
1808 pSettings->fConvertTabs = false;
1809 return VINF_SUCCESS;
1810
1811 case SCMOPT_FORCE_FINAL_EOL:
1812 pSettings->fForceFinalEol = true;
1813 return VINF_SUCCESS;
1814 case SCMOPT_NO_FORCE_FINAL_EOL:
1815 pSettings->fForceFinalEol = false;
1816 return VINF_SUCCESS;
1817
1818 case SCMOPT_FORCE_TRAILING_LINE:
1819 pSettings->fForceTrailingLine = true;
1820 return VINF_SUCCESS;
1821 case SCMOPT_NO_FORCE_TRAILING_LINE:
1822 pSettings->fForceTrailingLine = false;
1823 return VINF_SUCCESS;
1824
1825 case SCMOPT_STRIP_TRAILING_BLANKS:
1826 pSettings->fStripTrailingBlanks = true;
1827 return VINF_SUCCESS;
1828 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
1829 pSettings->fStripTrailingBlanks = false;
1830 return VINF_SUCCESS;
1831
1832 case SCMOPT_STRIP_TRAILING_LINES:
1833 pSettings->fStripTrailingLines = true;
1834 return VINF_SUCCESS;
1835 case SCMOPT_NO_STRIP_TRAILING_LINES:
1836 pSettings->fStripTrailingLines = false;
1837 return VINF_SUCCESS;
1838
1839 case SCMOPT_ONLY_SVN_DIRS:
1840 pSettings->fOnlySvnDirs = true;
1841 return VINF_SUCCESS;
1842 case SCMOPT_NOT_ONLY_SVN_DIRS:
1843 pSettings->fOnlySvnDirs = false;
1844 return VINF_SUCCESS;
1845
1846 case SCMOPT_TAB_SIZE:
1847 if ( pValueUnion->u8 < 1
1848 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
1849 {
1850 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
1851 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
1852 return VERR_OUT_OF_RANGE;
1853 }
1854 pSettings->cchTab = pValueUnion->u8;
1855 return VINF_SUCCESS;
1856
1857 case SCMOPT_FILTER_OUT_DIRS:
1858 case SCMOPT_FILTER_FILES:
1859 case SCMOPT_FILTER_OUT_FILES:
1860 {
1861 char **ppsz;
1862 switch (rc)
1863 {
1864 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
1865 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
1866 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
1867 }
1868
1869 /*
1870 * An empty string zaps the current list.
1871 */
1872 if (!*pValueUnion->psz)
1873 return RTStrATruncate(ppsz, 0);
1874
1875 /*
1876 * Non-empty strings are appended to the pattern list.
1877 *
1878 * Strip leading and trailing pattern separators before attempting
1879 * to append it. If it's just separators, don't do anything.
1880 */
1881 const char *pszSrc = pValueUnion->psz;
1882 while (*pszSrc == '|')
1883 pszSrc++;
1884 size_t cchSrc = strlen(pszSrc);
1885 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
1886 cchSrc--;
1887 if (!cchSrc)
1888 return VINF_SUCCESS;
1889
1890 return RTStrAAppendExN(ppsz, 2,
1891 "|", *ppsz && **ppsz ? 1 : 0,
1892 pszSrc, cchSrc);
1893 }
1894
1895 default:
1896 return VERR_GETOPT_UNKNOWN_OPTION;
1897 }
1898}
1899
1900/**
1901 * Parses an option string.
1902 *
1903 * @returns IPRT status code.
1904 * @param pBase The base settings structure to apply the options
1905 * to.
1906 * @param pszOptions The options to parse.
1907 */
1908static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
1909{
1910 int cArgs;
1911 char **papszArgs;
1912 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
1913 if (RT_SUCCESS(rc))
1914 {
1915 RTGETOPTUNION ValueUnion;
1916 RTGETOPTSTATE GetOptState;
1917 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
1918 if (RT_SUCCESS(rc))
1919 {
1920 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1921 {
1922 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
1923 if (RT_FAILURE(rc))
1924 break;
1925 }
1926 }
1927 RTGetOptArgvFree(papszArgs);
1928 }
1929
1930 return rc;
1931}
1932
1933/**
1934 * Parses an unterminated option string.
1935 *
1936 * @returns IPRT status code.
1937 * @param pBase The base settings structure to apply the options
1938 * to.
1939 * @param pchLine The line.
1940 * @param cchLine The line length.
1941 */
1942static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
1943{
1944 char *pszLine = RTStrDupN(pchLine, cchLine);
1945 if (!pszLine)
1946 return VERR_NO_MEMORY;
1947 int rc = scmSettingsBaseParseString(pBase, pszLine);
1948 RTStrFree(pszLine);
1949 return rc;
1950}
1951
1952/**
1953 * Verifies the options string.
1954 *
1955 * @returns IPRT status code.
1956 * @param pszOptions The options to verify .
1957 */
1958static int scmSettingsBaseVerifyString(const char *pszOptions)
1959{
1960 SCMSETTINGSBASE Base;
1961 int rc = scmSettingsBaseInit(&Base);
1962 if (RT_SUCCESS(rc))
1963 {
1964 rc = scmSettingsBaseParseString(&Base, pszOptions);
1965 scmSettingsBaseDelete(&Base);
1966 }
1967 return rc;
1968}
1969
1970/**
1971 * Loads settings found in editor and SCM settings directives within the
1972 * document (@a pStream).
1973 *
1974 * @returns IPRT status code.
1975 * @param pBase The settings base to load settings into.
1976 * @param pStream The stream to scan for settings directives.
1977 */
1978static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
1979{
1980 /** @todo Editor and SCM settings directives in documents. */
1981 return VINF_SUCCESS;
1982}
1983
1984/**
1985 * Creates a new settings file struct, cloning @a pSettings.
1986 *
1987 * @returns IPRT status code.
1988 * @param ppSettings Where to return the new struct.
1989 * @param pSettingsBase The settings to inherit from.
1990 */
1991static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
1992{
1993 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
1994 if (!pSettings)
1995 return VERR_NO_MEMORY;
1996 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
1997 if (RT_SUCCESS(rc))
1998 {
1999 pSettings->pDown = NULL;
2000 pSettings->pUp = NULL;
2001 pSettings->paPairs = NULL;
2002 pSettings->cPairs = 0;
2003 *ppSettings = pSettings;
2004 return VINF_SUCCESS;
2005 }
2006 RTMemFree(pSettings);
2007 return rc;
2008}
2009
2010/**
2011 * Destroys a settings structure.
2012 *
2013 * @param pSettings The settgins structure to destroy. NULL is OK.
2014 */
2015static void scmSettingsDestroy(PSCMSETTINGS pSettings)
2016{
2017 if (pSettings)
2018 {
2019 scmSettingsBaseDelete(&pSettings->Base);
2020 for (size_t i = 0; i < pSettings->cPairs; i++)
2021 {
2022 RTStrFree(pSettings->paPairs[i].pszPattern);
2023 RTStrFree(pSettings->paPairs[i].pszOptions);
2024 pSettings->paPairs[i].pszPattern = NULL;
2025 pSettings->paPairs[i].pszOptions = NULL;
2026 }
2027 RTMemFree(pSettings->paPairs);
2028 pSettings->paPairs = NULL;
2029 RTMemFree(pSettings);
2030 }
2031}
2032
2033/**
2034 * Adds a pattern/options pair to the settings structure.
2035 *
2036 * @returns IPRT status code.
2037 * @param pSettings The settings.
2038 * @param pchLine The line containing the unparsed pair.
2039 * @param cchLine The length of the line.
2040 */
2041static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
2042{
2043 /*
2044 * Split the string.
2045 */
2046 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
2047 if (!pchOptions)
2048 return VERR_INVALID_PARAMETER;
2049 size_t cchPattern = pchOptions - pchLine;
2050 size_t cchOptions = cchLine - cchPattern - 1;
2051 pchOptions++;
2052
2053 /* strip spaces everywhere */
2054 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
2055 cchPattern--;
2056 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
2057 cchPattern--, pchLine++;
2058
2059 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
2060 cchOptions--;
2061 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
2062 cchOptions--, pchOptions++;
2063
2064 /* Quietly ignore empty patterns and empty options. */
2065 if (!cchOptions || !cchPattern)
2066 return VINF_SUCCESS;
2067
2068 /*
2069 * Add the pair and verify the option string.
2070 */
2071 uint32_t iPair = pSettings->cPairs;
2072 if ((iPair % 32) == 0)
2073 {
2074 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
2075 if (!pvNew)
2076 return VERR_NO_MEMORY;
2077 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
2078 }
2079
2080 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
2081 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
2082 int rc;
2083 if ( pSettings->paPairs[iPair].pszPattern
2084 && pSettings->paPairs[iPair].pszOptions)
2085 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
2086 else
2087 rc = VERR_NO_MEMORY;
2088 if (RT_SUCCESS(rc))
2089 pSettings->cPairs = iPair + 1;
2090 else
2091 {
2092 RTStrFree(pSettings->paPairs[iPair].pszPattern);
2093 RTStrFree(pSettings->paPairs[iPair].pszOptions);
2094 }
2095 return rc;
2096}
2097
2098/**
2099 * Loads in the settings from @a pszFilename.
2100 *
2101 * @returns IPRT status code.
2102 * @param pSettings Where to load the settings file.
2103 * @param pszFilename The file to load.
2104 */
2105static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
2106{
2107 SCMSTREAM Stream;
2108 int rc = ScmStreamInitForReading(&Stream, pszFilename);
2109 if (RT_FAILURE(rc))
2110 {
2111 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
2112 return rc;
2113 }
2114
2115 SCMEOL enmEol;
2116 const char *pchLine;
2117 size_t cchLine;
2118 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
2119 {
2120 /* Ignore leading spaces. */
2121 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
2122 pchLine++, cchLine--;
2123
2124 /* Ignore empty lines and comment lines. */
2125 if (cchLine < 1 || *pchLine == '#')
2126 continue;
2127
2128 /* What kind of line is it? */
2129 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
2130 if (pchColon)
2131 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
2132 else
2133 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
2134 if (RT_FAILURE(rc))
2135 {
2136 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
2137 break;
2138 }
2139 }
2140
2141 if (RT_SUCCESS(rc))
2142 {
2143 rc = ScmStreamGetStatus(&Stream);
2144 if (RT_FAILURE(rc))
2145 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
2146 }
2147
2148 ScmStreamDelete(&Stream);
2149 return rc;
2150}
2151
2152/**
2153 * Parse the specified settings file creating a new settings struct from it.
2154 *
2155 * @returns IPRT status code
2156 * @param ppSettings Where to return the new settings.
2157 * @param pszFilename The file to parse.
2158 * @param pSettingsBase The base settings we inherit from.
2159 */
2160static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
2161{
2162 PSCMSETTINGS pSettings;
2163 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
2164 if (RT_SUCCESS(rc))
2165 {
2166 rc = scmSettingsLoadFile(pSettings, pszFilename);
2167 if (RT_SUCCESS(rc))
2168 {
2169 *ppSettings = pSettings;
2170 return VINF_SUCCESS;
2171 }
2172
2173 scmSettingsDestroy(pSettings);
2174 }
2175 *ppSettings = NULL;
2176 return rc;
2177}
2178
2179
2180/**
2181 * Create an initial settings structure when starting processing a new file or
2182 * directory.
2183 *
2184 * This will look for .scm-settings files from the root and down to the
2185 * specified directory, combining them into the returned settings structure.
2186 *
2187 * @returns IPRT status code.
2188 * @param ppSettings Where to return the pointer to the top stack
2189 * object.
2190 * @param pBaseSettings The base settings we inherit from (globals
2191 * typically).
2192 * @param pszPath The absolute path to the new directory or file.
2193 */
2194static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
2195{
2196 /*
2197 * We'll be working with a stack copy of the path.
2198 */
2199 char szFile[RTPATH_MAX];
2200 size_t cchDir = strlen(pszPath);
2201 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
2202 return VERR_FILENAME_TOO_LONG;
2203
2204 /*
2205 * Create the bottom-most settings.
2206 */
2207 PSCMSETTINGS pSettings;
2208 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
2209 if (RT_FAILURE(rc))
2210 return rc;
2211
2212 /*
2213 * Enumerate the path components from the root and down. Load any setting
2214 * files we find.
2215 */
2216 size_t cComponents = RTPathCountComponents(pszPath);
2217 for (size_t i = 1; i <= cComponents; i++)
2218 {
2219 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
2220 if (RT_SUCCESS(rc))
2221 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
2222 if (RT_FAILURE(rc))
2223 break;
2224 if (RTFileExists(szFile))
2225 {
2226 rc = scmSettingsLoadFile(pSettings, szFile);
2227 if (RT_FAILURE(rc))
2228 break;
2229 }
2230 }
2231
2232 if (RT_SUCCESS(rc))
2233 *ppSettings = pSettings;
2234 else
2235 scmSettingsDestroy(pSettings);
2236 return rc;
2237}
2238
2239/**
2240 * Pushes a new settings set onto the stack.
2241 *
2242 * @param ppSettingsStack The pointer to the pointer to the top stack
2243 * element. This will be used as input and output.
2244 * @param pSettings The settings to push onto the stack.
2245 */
2246static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
2247{
2248 PSCMSETTINGS pOld = *ppSettingsStack;
2249 pSettings->pDown = pOld;
2250 pSettings->pUp = NULL;
2251 if (pOld)
2252 pOld->pUp = pSettings;
2253 *ppSettingsStack = pSettings;
2254}
2255
2256/**
2257 * Pushes the settings of the specified directory onto the stack.
2258 *
2259 * We will load any .scm-settings in the directory. A stack entry is added even
2260 * if no settings file was found.
2261 *
2262 * @returns IPRT status code.
2263 * @param ppSettingsStack The pointer to the pointer to the top stack
2264 * element. This will be used as input and output.
2265 * @param pszDir The directory to do this for.
2266 */
2267static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
2268{
2269 char szFile[RTPATH_MAX];
2270 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
2271 if (RT_SUCCESS(rc))
2272 {
2273 PSCMSETTINGS pSettings;
2274 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
2275 if (RT_SUCCESS(rc))
2276 {
2277 if (RTFileExists(szFile))
2278 rc = scmSettingsLoadFile(pSettings, szFile);
2279 if (RT_SUCCESS(rc))
2280 {
2281 scmSettingsStackPush(ppSettingsStack, pSettings);
2282 return VINF_SUCCESS;
2283 }
2284
2285 scmSettingsDestroy(pSettings);
2286 }
2287 }
2288 return rc;
2289}
2290
2291
2292/**
2293 * Pops a settings set off the stack.
2294 *
2295 * @returns The popped setttings.
2296 * @param ppSettingsStack The pointer to the pointer to the top stack
2297 * element. This will be used as input and output.
2298 */
2299static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
2300{
2301 PSCMSETTINGS pRet = *ppSettingsStack;
2302 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
2303 *ppSettingsStack = pNew;
2304 if (pNew)
2305 pNew->pUp = NULL;
2306 if (pRet)
2307 {
2308 pRet->pUp = NULL;
2309 pRet->pDown = NULL;
2310 }
2311 return pRet;
2312}
2313
2314/**
2315 * Pops and destroys the top entry of the stack.
2316 *
2317 * @param ppSettingsStack The pointer to the pointer to the top stack
2318 * element. This will be used as input and output.
2319 */
2320static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
2321{
2322 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
2323}
2324
2325/**
2326 * Constructs the base settings for the specified file name.
2327 *
2328 * @returns IPRT status code.
2329 * @param pSettingsStack The top element on the settings stack.
2330 * @param pszFilename The file name.
2331 * @param pszBasename The base name (pointer within @a pszFilename).
2332 * @param cchBasename The length of the base name. (For passing to
2333 * RTStrSimplePatternMultiMatch.)
2334 * @param pBase Base settings to initialize.
2335 */
2336static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
2337 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
2338{
2339 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
2340 if (RT_SUCCESS(rc))
2341 {
2342 /* find the bottom entry in the stack. */
2343 PCSCMSETTINGS pCur = pSettingsStack;
2344 while (pCur->pDown)
2345 pCur = pCur->pDown;
2346
2347 /* Work our way up thru the stack and look for matching pairs. */
2348 while (pCur)
2349 {
2350 size_t const cPairs = pCur->cPairs;
2351 if (cPairs)
2352 {
2353 for (size_t i = 0; i < cPairs; i++)
2354 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
2355 pszBasename, cchBasename, NULL)
2356 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
2357 pszFilename, RTSTR_MAX, NULL))
2358 {
2359 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
2360 if (RT_FAILURE(rc))
2361 break;
2362 }
2363 if (RT_FAILURE(rc))
2364 break;
2365 }
2366
2367 /* advance */
2368 pCur = pCur->pUp;
2369 }
2370 }
2371 if (RT_FAILURE(rc))
2372 scmSettingsBaseDelete(pBase);
2373 return rc;
2374}
2375
2376
2377/* -=-=-=-=-=- misc -=-=-=-=-=- */
2378
2379
2380/**
2381 * Prints a verbose message if the level is high enough.
2382 *
2383 * @param pState The rewrite state. Optional.
2384 * @param iLevel The required verbosity level.
2385 * @param pszFormat The message format string. Can be NULL if we
2386 * only want to trigger the per file message.
2387 * @param ... Format arguments.
2388 */
2389static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
2390{
2391 if (iLevel <= g_iVerbosity)
2392 {
2393 if (pState && !pState->fFirst)
2394 {
2395 RTPrintf("%s: info: Rewriting '%s'...\n", g_szProgName, pState->pszFilename);
2396 pState->fFirst = true;
2397 }
2398 if (pszFormat)
2399 {
2400 RTPrintf("%s: info: ", g_szProgName);
2401 va_list va;
2402 va_start(va, pszFormat);
2403 RTPrintfV(pszFormat, va);
2404 va_end(va);
2405 }
2406 }
2407}
2408
2409
2410/* -=-=-=-=-=- rewriters -=-=-=-=-=- */
2411
2412
2413/**
2414 * Strip trailing blanks (space & tab).
2415 *
2416 * @returns True if modified, false if not.
2417 * @param pIn The input stream.
2418 * @param pOut The output stream.
2419 * @param pSettings The settings.
2420 */
2421static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2422{
2423 if (!pSettings->fStripTrailingBlanks)
2424 return false;
2425
2426 bool fModified = false;
2427 SCMEOL enmEol;
2428 size_t cchLine;
2429 const char *pchLine;
2430 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2431 {
2432 int rc;
2433 if ( cchLine == 0
2434 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
2435 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2436 else
2437 {
2438 cchLine--;
2439 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
2440 cchLine--;
2441 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2442 fModified = true;
2443 }
2444 if (RT_FAILURE(rc))
2445 return false;
2446 }
2447 if (fModified)
2448 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
2449 return fModified;
2450}
2451
2452/**
2453 * Expand tabs.
2454 *
2455 * @returns True if modified, false if not.
2456 * @param pIn The input stream.
2457 * @param pOut The output stream.
2458 * @param pSettings The settings.
2459 */
2460static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2461{
2462 if (!pSettings->fConvertTabs)
2463 return false;
2464
2465 size_t const cchTab = pSettings->cchTab;
2466 bool fModified = false;
2467 SCMEOL enmEol;
2468 size_t cchLine;
2469 const char *pchLine;
2470 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2471 {
2472 int rc;
2473 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
2474 if (!pchTab)
2475 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2476 else
2477 {
2478 size_t offTab = 0;
2479 const char *pchChunk = pchLine;
2480 for (;;)
2481 {
2482 size_t cchChunk = pchTab - pchChunk;
2483 offTab += cchChunk;
2484 ScmStreamWrite(pOut, pchChunk, cchChunk);
2485
2486 size_t cchToTab = cchTab - offTab % cchTab;
2487 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
2488 offTab += cchToTab;
2489
2490 pchChunk = pchTab + 1;
2491 size_t cchLeft = cchLine - (pchChunk - pchLine);
2492 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
2493 if (!pchTab)
2494 {
2495 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
2496 break;
2497 }
2498 }
2499
2500 fModified = true;
2501 }
2502 if (RT_FAILURE(rc))
2503 return false;
2504 }
2505 if (fModified)
2506 ScmVerbose(pState, 2, " * Expanded tabs\n");
2507 return fModified;
2508}
2509
2510/**
2511 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
2512 *
2513 * @returns true if modifications were made, false if not.
2514 * @param pIn The input stream.
2515 * @param pOut The output stream.
2516 * @param pSettings The settings.
2517 * @param enmDesiredEol The desired end of line indicator type.
2518 */
2519static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings, SCMEOL enmDesiredEol)
2520{
2521 if (!pSettings->fConvertEol)
2522 return false;
2523
2524 bool fModified = false;
2525 SCMEOL enmEol;
2526 size_t cchLine;
2527 const char *pchLine;
2528 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2529 {
2530 if ( enmEol != enmDesiredEol
2531 && enmEol != SCMEOL_NONE)
2532 {
2533 fModified = true;
2534 enmEol = enmDesiredEol;
2535 }
2536 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2537 if (RT_FAILURE(rc))
2538 return false;
2539 }
2540 if (fModified)
2541 ScmVerbose(pState, 2, " * Converted EOL markers\n");
2542 return fModified;
2543}
2544
2545/**
2546 * Force native end of line indicator.
2547 *
2548 * @returns true if modifications were made, false if not.
2549 * @param pIn The input stream.
2550 * @param pOut The output stream.
2551 * @param pSettings The settings.
2552 */
2553static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2554{
2555#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
2556 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF);
2557#else
2558 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF);
2559#endif
2560}
2561
2562/**
2563 * Force the stream to use LF as the end of line indicator.
2564 *
2565 * @returns true if modifications were made, false if not.
2566 * @param pIn The input stream.
2567 * @param pOut The output stream.
2568 * @param pSettings The settings.
2569 */
2570static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2571{
2572 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF);
2573}
2574
2575/**
2576 * Force the stream to use CRLF as the end of line indicator.
2577 *
2578 * @returns true if modifications were made, false if not.
2579 * @param pIn The input stream.
2580 * @param pOut The output stream.
2581 * @param pSettings The settings.
2582 */
2583static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2584{
2585 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF);
2586}
2587
2588/**
2589 * Strip trailing blank lines and/or make sure there is exactly one blank line
2590 * at the end of the file.
2591 *
2592 * @returns true if modifications were made, false if not.
2593 * @param pIn The input stream.
2594 * @param pOut The output stream.
2595 * @param pSettings The settings.
2596 *
2597 * @remarks ASSUMES trailing white space has been removed already.
2598 */
2599static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2600{
2601 if ( !pSettings->fStripTrailingLines
2602 && !pSettings->fForceTrailingLine
2603 && !pSettings->fForceFinalEol)
2604 return false;
2605
2606 size_t const cLines = ScmStreamCountLines(pIn);
2607
2608 /* Empty files remains empty. */
2609 if (cLines <= 1)
2610 return false;
2611
2612 /* Figure out if we need to adjust the number of lines or not. */
2613 size_t cLinesNew = cLines;
2614
2615 if ( pSettings->fStripTrailingLines
2616 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
2617 {
2618 while ( cLinesNew > 1
2619 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
2620 cLinesNew--;
2621 }
2622
2623 if ( pSettings->fForceTrailingLine
2624 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
2625 cLinesNew++;
2626
2627 bool fFixMissingEol = pSettings->fForceFinalEol
2628 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
2629
2630 if ( !fFixMissingEol
2631 && cLines == cLinesNew)
2632 return false;
2633
2634 /* Copy the number of lines we've arrived at. */
2635 ScmStreamRewindForReading(pIn);
2636
2637 size_t cCopied = RT_MIN(cLinesNew, cLines);
2638 ScmStreamCopyLines(pOut, pIn, cCopied);
2639
2640 if (cCopied != cLinesNew)
2641 {
2642 while (cCopied++ < cLinesNew)
2643 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
2644 }
2645 /* Fix missing EOL if required. */
2646 else if (fFixMissingEol)
2647 {
2648 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
2649 ScmStreamWrite(pOut, "\n", 1);
2650 else
2651 ScmStreamWrite(pOut, "\r\n", 2);
2652 }
2653
2654 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
2655 return true;
2656}
2657
2658/**
2659 * Makefile.kup are empty files, enforce this.
2660 *
2661 * @returns true if modifications were made, false if not.
2662 * @param pIn The input stream.
2663 * @param pOut The output stream.
2664 * @param pSettings The settings.
2665 */
2666static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2667{
2668 /* These files should be zero bytes. */
2669 if (pIn->cb == 0)
2670 return false;
2671 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
2672 return true;
2673}
2674
2675/**
2676 * Rewrite a kBuild makefile.
2677 *
2678 * @returns true if modifications were made, false if not.
2679 * @param pIn The input stream.
2680 * @param pOut The output stream.
2681 * @param pSettings The settings.
2682 *
2683 * @todo
2684 *
2685 * Ideas for Makefile.kmk and Config.kmk:
2686 * - sort if1of/ifn1of sets.
2687 * - line continuation slashes should only be preceeded by one space.
2688 */
2689static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2690{
2691 return false;
2692}
2693
2694/**
2695 * Rewrite a C/C++ source or header file.
2696 *
2697 * @returns true if modifications were made, false if not.
2698 * @param pIn The input stream.
2699 * @param pOut The output stream.
2700 * @param pSettings The settings.
2701 *
2702 * @todo
2703 *
2704 * Ideas for C/C++:
2705 * - space after if, while, for, switch
2706 * - spaces in for (i=0;i<x;i++)
2707 * - complex conditional, bird style.
2708 * - remove unnecessary parentheses.
2709 * - sort defined RT_OS_*|| and RT_ARCH
2710 * - sizeof without parenthesis.
2711 * - defined without parenthesis.
2712 * - trailing spaces.
2713 * - parameter indentation.
2714 * - space after comma.
2715 * - while (x--); -> multi line + comment.
2716 * - else statement;
2717 * - space between function and left parenthesis.
2718 * - TODO, XXX, @todo cleanup.
2719 * - Space before/after '*'.
2720 * - ensure new line at end of file.
2721 * - Indentation of precompiler statements (#ifdef, #defines).
2722 * - space between functions.
2723 */
2724static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2725{
2726
2727 return false;
2728}
2729
2730/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
2731
2732/**
2733 * Processes a file.
2734 *
2735 * @returns IPRT status code.
2736 * @param pszFilename The file name.
2737 * @param pszBasename The base name (pointer within @a pszFilename).
2738 * @param cchBasename The length of the base name. (For passing to
2739 * RTStrSimplePatternMultiMatch.)
2740 * @param pBaseSettings The base settings to use. It's OK to modify
2741 * these.
2742 */
2743static int scmProcessFileInner(const char *pszFilename, const char *pszBasename, size_t cchBasename,
2744 PSCMSETTINGSBASE pBaseSettings)
2745{
2746 /*
2747 * Init the rewriter state data.
2748 */
2749 SCMRWSTATE State;
2750 State.fFirst = false;
2751 State.pszFilename = pszFilename;
2752
2753 /*
2754 * Do the file level filtering.
2755 */
2756 if ( pBaseSettings->pszFilterFiles
2757 && *pBaseSettings->pszFilterFiles
2758 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
2759 {
2760 ScmVerbose(NULL, 5, "file filter mismatch: \"%s\"\n", pszFilename);
2761 return VINF_SUCCESS;
2762 }
2763 if ( pBaseSettings->pszFilterOutFiles
2764 && *pBaseSettings->pszFilterOutFiles
2765 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
2766 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
2767 {
2768 ScmVerbose(NULL, 5, "file filter out: \"%s\"\n", pszFilename);
2769 return VINF_SUCCESS;
2770 }
2771
2772 /*
2773 * Try find a matching rewrite config for this filename.
2774 */
2775 PCSCMCFGENTRY pCfg = NULL;
2776 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2777 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
2778 {
2779 pCfg = &g_aConfigs[iCfg];
2780 break;
2781 }
2782 if (!pCfg)
2783 {
2784 ScmVerbose(NULL, 4, "No rewriters configured for \"%s\"\n", pszFilename);
2785 return VINF_SUCCESS;
2786 }
2787 ScmVerbose(&State, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
2788
2789 /*
2790 * Create an input stream from the file and check that it's text.
2791 */
2792 SCMSTREAM Stream1;
2793 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
2794 if (RT_FAILURE(rc))
2795 {
2796 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
2797 return rc;
2798 }
2799 if (ScmStreamIsText(&Stream1))
2800 {
2801 ScmVerbose(&State, 3, NULL);
2802
2803 /*
2804 * Gather SCM and editor settings from the stream.
2805 */
2806 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
2807 if (RT_SUCCESS(rc))
2808 {
2809 ScmStreamRewindForReading(&Stream1);
2810
2811 /*
2812 * Create two more streams for output and push the text thru all the
2813 * rewriters, switching the two streams around when something is
2814 * actually rewritten. Stream1 remains unchanged.
2815 */
2816 SCMSTREAM Stream2;
2817 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
2818 if (RT_SUCCESS(rc))
2819 {
2820 SCMSTREAM Stream3;
2821 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
2822 if (RT_SUCCESS(rc))
2823 {
2824 bool fModified = false;
2825 PSCMSTREAM pIn = &Stream1;
2826 PSCMSTREAM pOut = &Stream2;
2827 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
2828 {
2829 bool fRc = pCfg->papfnRewriter[iRw](&State, pIn, pOut, pBaseSettings);
2830 if (fRc)
2831 {
2832 PSCMSTREAM pTmp = pOut;
2833 pOut = pIn == &Stream1 ? &Stream3 : pIn;
2834 pIn = pTmp;
2835 fModified = true;
2836 }
2837 ScmStreamRewindForReading(pIn);
2838 ScmStreamRewindForWriting(pOut);
2839 }
2840
2841 /*
2842 * If rewritten, write it back to disk.
2843 */
2844 if (fModified)
2845 {
2846 if (!g_fDryRun)
2847 {
2848 ScmVerbose(&State, 1, "Writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
2849 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
2850 if (RT_FAILURE(rc))
2851 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
2852 }
2853 else
2854 {
2855 ScmVerbose(&State, 1, NULL);
2856 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
2857 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
2858 ScmVerbose(&State, 3, "Would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
2859 }
2860 }
2861 else
2862 ScmVerbose(&State, 3, "No change\n", pszFilename);
2863
2864 ScmStreamDelete(&Stream3);
2865 }
2866 else
2867 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2868 ScmStreamDelete(&Stream2);
2869 }
2870 else
2871 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2872 }
2873 else
2874 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
2875 }
2876 else
2877 ScmVerbose(&State, 4, "not text file: \"%s\"\n", pszFilename);
2878 ScmStreamDelete(&Stream1);
2879
2880 return rc;
2881}
2882
2883/**
2884 * Processes a file.
2885 *
2886 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
2887 * directory recursion method.
2888 *
2889 * @returns IPRT status code.
2890 * @param pszFilename The file name.
2891 * @param pszBasename The base name (pointer within @a pszFilename).
2892 * @param cchBasename The length of the base name. (For passing to
2893 * RTStrSimplePatternMultiMatch.)
2894 * @param pSettingsStack The settings stack (pointer to the top element).
2895 */
2896static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
2897 PSCMSETTINGS pSettingsStack)
2898{
2899 SCMSETTINGSBASE Base;
2900 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
2901 if (RT_SUCCESS(rc))
2902 {
2903 rc = scmProcessFileInner(pszFilename, pszBasename, cchBasename, &Base);
2904 scmSettingsBaseDelete(&Base);
2905 }
2906 return rc;
2907}
2908
2909
2910/**
2911 * Tries to correct RTDIRENTRY_UNKNOWN.
2912 *
2913 * @returns Corrected type.
2914 * @param pszPath The path to the object in question.
2915 */
2916static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
2917{
2918 RTFSOBJINFO Info;
2919 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
2920 if (RT_FAILURE(rc))
2921 return RTDIRENTRYTYPE_UNKNOWN;
2922 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
2923 return RTDIRENTRYTYPE_DIRECTORY;
2924 if (RTFS_IS_FILE(Info.Attr.fMode))
2925 return RTDIRENTRYTYPE_FILE;
2926 return RTDIRENTRYTYPE_UNKNOWN;
2927}
2928
2929/**
2930 * Recurse into a sub-directory and process all the files and directories.
2931 *
2932 * @returns IPRT status code.
2933 * @param pszBuf Path buffer containing the directory path on
2934 * entry. This ends with a dot. This is passed
2935 * along when recusing in order to save stack space
2936 * and avoid needless copying.
2937 * @param cchDir Length of our path in pszbuf.
2938 * @param pEntry Directory entry buffer. This is also passed
2939 * along when recursing to save stack space.
2940 * @param pSettingsStack The settings stack (pointer to the top element).
2941 * @param iRecursion The recursion depth. This is used to restrict
2942 * the recursions.
2943 */
2944static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
2945 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
2946{
2947 int rc;
2948 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
2949
2950 /*
2951 * Make sure we stop somewhere.
2952 */
2953 if (iRecursion > 128)
2954 {
2955 RTMsgError("recursion too deep: %d\n", iRecursion);
2956 return VINF_SUCCESS; /* ignore */
2957 }
2958
2959 /*
2960 * Check if it's excluded by --only-svn-dir.
2961 */
2962 if (pSettingsStack->Base.fOnlySvnDirs)
2963 {
2964 rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
2965 if (RT_FAILURE(rc))
2966 {
2967 RTMsgError("RTPathAppend: %Rrc\n", rc);
2968 return rc;
2969 }
2970 if (!RTDirExists(pszBuf))
2971 return VINF_SUCCESS;
2972
2973 Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
2974 pszBuf[cchDir] = '\0';
2975 pszBuf[cchDir - 1] = '.';
2976 }
2977
2978 /*
2979 * Try open and read the directory.
2980 */
2981 PRTDIR pDir;
2982 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE);
2983 if (RT_FAILURE(rc))
2984 {
2985 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
2986 return rc;
2987 }
2988 for (;;)
2989 {
2990 /* Read the next entry. */
2991 rc = RTDirRead(pDir, pEntry, NULL);
2992 if (RT_FAILURE(rc) && rc != VERR_NO_MORE_FILES)
2993 RTMsgError("RTDirRead -> %Rrc\n", rc);
2994 if (RT_FAILURE(rc))
2995 break;
2996
2997 /* Skip '.' and '..'. */
2998 if ( pEntry->szName[0] == '.'
2999 && ( pEntry->cbName == 1
3000 || ( pEntry->cbName == 2
3001 && pEntry->szName[1] == '.')))
3002 continue;
3003
3004 /* Enter it into the buffer so we've got a full name to work
3005 with when needed. */
3006 if (pEntry->cbName + cchDir >= RTPATH_MAX)
3007 {
3008 RTMsgError("Skipping too long entry: %s", pEntry->szName);
3009 continue;
3010 }
3011 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
3012
3013 /* Figure the type. */
3014 RTDIRENTRYTYPE enmType = pEntry->enmType;
3015 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
3016 enmType = scmFigureUnknownType(pszBuf);
3017
3018 /* Process the file or directory, skip the rest. */
3019 if (enmType == RTDIRENTRYTYPE_FILE)
3020 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
3021 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
3022 {
3023 /* Append the dot for the benefit of the pattern matching. */
3024 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
3025 {
3026 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
3027 continue;
3028 }
3029 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
3030 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
3031
3032 if ( !pSettingsStack->Base.pszFilterOutDirs
3033 || !*pSettingsStack->Base.pszFilterOutDirs
3034 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
3035 pEntry->szName, pEntry->cbName, NULL)
3036 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
3037 pszBuf, cchSubDir, NULL)
3038 )
3039 )
3040 {
3041 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
3042 if (RT_SUCCESS(rc))
3043 {
3044 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
3045 scmSettingsStackPopAndDestroy(&pSettingsStack);
3046 }
3047 }
3048 }
3049 if (RT_FAILURE(rc))
3050 break;
3051 }
3052 RTDirClose(pDir);
3053 return RT_SUCCESS(rc) ? 0 : 1;
3054
3055}
3056
3057/**
3058 * Process a directory tree.
3059 *
3060 * @returns IPRT status code.
3061 * @param pszDir The directory to start with. This is pointer to
3062 * a RTPATH_MAX sized buffer.
3063 */
3064static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
3065{
3066 /*
3067 * Setup the recursion.
3068 */
3069 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
3070 if (RT_SUCCESS(rc))
3071 {
3072 RTDIRENTRY Entry;
3073 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
3074 }
3075 else
3076 RTMsgError("RTPathAppend: %Rrc\n", rc);
3077 return rc;
3078}
3079
3080
3081/**
3082 * Processes a file or directory specified as an command line argument.
3083 *
3084 * @returns IPRT status code
3085 * @param pszSomething What we found in the commad line arguments.
3086 * @param pSettingsStack The settings stack (pointer to the top element).
3087 */
3088static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
3089{
3090 char szBuf[RTPATH_MAX];
3091 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
3092 if (RT_SUCCESS(rc))
3093 {
3094 PSCMSETTINGS pSettings;
3095 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
3096 if (RT_SUCCESS(rc))
3097 {
3098 scmSettingsStackPush(&pSettingsStack, pSettings);
3099
3100 if (RTFileExists(szBuf))
3101 {
3102 const char *pszBasename = RTPathFilename(szBuf);
3103 if (pszBasename)
3104 {
3105 size_t cchBasename = strlen(pszBasename);
3106 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
3107 }
3108 else
3109 {
3110 RTMsgError("RTPathFilename: NULL\n");
3111 rc = VERR_IS_A_DIRECTORY;
3112 }
3113 }
3114 else
3115 rc = scmProcessDirTree(szBuf, pSettingsStack);
3116
3117 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
3118 Assert(pPopped == pSettings);
3119 scmSettingsDestroy(pSettings);
3120 }
3121 else
3122 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
3123 }
3124 else
3125 RTMsgError("RTPathAbs: %Rrc\n", rc);
3126 return rc;
3127}
3128
3129int main(int argc, char **argv)
3130{
3131 int rc = RTR3Init();
3132 if (RT_FAILURE(rc))
3133 return 1;
3134
3135 /*
3136 * Init the settings.
3137 */
3138 PSCMSETTINGS pSettings;
3139 rc = scmSettingsCreate(&pSettings, &g_Defaults);
3140 if (RT_FAILURE(rc))
3141 {
3142 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
3143 return 1;
3144 }
3145
3146 /*
3147 * Parse arguments and process input in order (because this is the only
3148 * thing that works at the moment).
3149 */
3150 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
3151 {
3152 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
3153 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
3154 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
3155 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
3156 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
3157 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
3158 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
3159 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
3160 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
3161 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
3162 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
3163 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
3164 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
3165 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
3166 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
3167 };
3168 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
3169
3170 RTGETOPTUNION ValueUnion;
3171 RTGETOPTSTATE GetOptState;
3172 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
3173 AssertReleaseRCReturn(rc, 1);
3174 size_t cProcessed = 0;
3175
3176 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
3177 {
3178 switch (rc)
3179 {
3180 case 'd':
3181 g_fDryRun = true;
3182 break;
3183 case 'D':
3184 g_fDryRun = false;
3185 break;
3186
3187 case 'f':
3188 g_pszFileFilter = ValueUnion.psz;
3189 break;
3190
3191 case 'h':
3192 RTPrintf("VirtualBox Source Code Massager\n"
3193 "\n"
3194 "Usage: %s [options] <files & dirs>\n"
3195 "\n"
3196 "Options:\n", g_szProgName);
3197 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
3198 {
3199 bool fAdvanceTwo = false;
3200 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
3201 {
3202 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
3203 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
3204 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL);
3205 if (fAdvanceTwo)
3206 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
3207 else
3208 RTPrintf(" %s\n", s_aOpts[i].pszLong);
3209 }
3210 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
3211 RTPrintf(" %s string\n", s_aOpts[i].pszLong);
3212 else
3213 RTPrintf(" %s value\n", s_aOpts[i].pszLong);
3214 switch (s_aOpts[i].iShort)
3215 {
3216 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
3217 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
3218 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
3219 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
3220 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
3221 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
3222 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
3223 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
3224 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
3225 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
3226 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
3227 }
3228 i += fAdvanceTwo;
3229 }
3230 return 1;
3231
3232 case 'q':
3233 g_iVerbosity = 0;
3234 break;
3235
3236 case 'v':
3237 g_iVerbosity++;
3238 break;
3239
3240 case 'V':
3241 {
3242 /* The following is assuming that svn does it's job here. */
3243 static const char s_szRev[] = "$Revision: 26516 $";
3244 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
3245 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
3246 return 0;
3247 }
3248
3249 case SCMOPT_DIFF_IGNORE_EOL:
3250 g_fDiffIgnoreEol = true;
3251 break;
3252 case SCMOPT_DIFF_NO_IGNORE_EOL:
3253 g_fDiffIgnoreEol = false;
3254 break;
3255
3256 case SCMOPT_DIFF_IGNORE_SPACE:
3257 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
3258 break;
3259 case SCMOPT_DIFF_NO_IGNORE_SPACE:
3260 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
3261 break;
3262
3263 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
3264 g_fDiffIgnoreLeadingWS = true;
3265 break;
3266 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
3267 g_fDiffIgnoreLeadingWS = false;
3268 break;
3269
3270 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
3271 g_fDiffIgnoreTrailingWS = true;
3272 break;
3273 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
3274 g_fDiffIgnoreTrailingWS = false;
3275 break;
3276
3277 case SCMOPT_DIFF_SPECIAL_CHARS:
3278 g_fDiffSpecialChars = true;
3279 break;
3280 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
3281 g_fDiffSpecialChars = false;
3282 break;
3283
3284 case VINF_GETOPT_NOT_OPTION:
3285 {
3286 if (!g_fDryRun)
3287 {
3288 if (!cProcessed)
3289 {
3290 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
3291 "%s: there is a slight risk that bugs or a full disk may cause\n"
3292 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
3293 "%s: all your changes already. If you didn't, then don't blame\n"
3294 "%s: anyone for not warning you!\n"
3295 "%s:\n"
3296 "%s: Press any key to continue...\n",
3297 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
3298 g_szProgName, g_szProgName);
3299 RTStrmGetCh(g_pStdIn);
3300 }
3301 cProcessed++;
3302 }
3303 rc = scmProcessSomething(ValueUnion.psz, pSettings);
3304 if (RT_FAILURE(rc))
3305 return rc;
3306 break;
3307 }
3308
3309 default:
3310 {
3311 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
3312 if (RT_SUCCESS(rc2))
3313 break;
3314 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
3315 return 2;
3316 return RTGetOptPrintError(rc, &ValueUnion);
3317 }
3318 }
3319 }
3320
3321 scmSettingsDestroy(pSettings);
3322 return 0;
3323}
3324
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette