VirtualBox

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

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

scm: better help, correct defaults, verbose message cleanup.

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