VirtualBox

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

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

scm: resource files.

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

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