VirtualBox

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

Last change on this file since 59410 was 57369, checked in by vboxsync, 9 years ago

scm: better help

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.6 KB
Line 
1/* $Id: scm.cpp 57369 2015-08-14 18:18:57Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2015 Oracle Corporation
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
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/assert.h>
23#include <iprt/ctype.h>
24#include <iprt/dir.h>
25#include <iprt/env.h>
26#include <iprt/file.h>
27#include <iprt/err.h>
28#include <iprt/getopt.h>
29#include <iprt/initterm.h>
30#include <iprt/mem.h>
31#include <iprt/message.h>
32#include <iprt/param.h>
33#include <iprt/path.h>
34#include <iprt/process.h>
35#include <iprt/stream.h>
36#include <iprt/string.h>
37
38#include "scm.h"
39#include "scmdiff.h"
40
41
42/*********************************************************************************************************************************
43* Defined Constants And Macros *
44*********************************************************************************************************************************/
45/** The name of the settings files. */
46#define SCM_SETTINGS_FILENAME ".scm-settings"
47
48
49/*********************************************************************************************************************************
50* Structures and Typedefs *
51*********************************************************************************************************************************/
52
53/**
54 * Option identifiers.
55 *
56 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
57 * clear. So, the option setting a flag (boolean) will have an even
58 * number and the one clearing it will have an odd number.
59 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
60 */
61typedef enum SCMOPT
62{
63 SCMOPT_CONVERT_EOL = 10000,
64 SCMOPT_NO_CONVERT_EOL,
65 SCMOPT_CONVERT_TABS,
66 SCMOPT_NO_CONVERT_TABS,
67 SCMOPT_FORCE_FINAL_EOL,
68 SCMOPT_NO_FORCE_FINAL_EOL,
69 SCMOPT_FORCE_TRAILING_LINE,
70 SCMOPT_NO_FORCE_TRAILING_LINE,
71 SCMOPT_STRIP_TRAILING_BLANKS,
72 SCMOPT_NO_STRIP_TRAILING_BLANKS,
73 SCMOPT_STRIP_TRAILING_LINES,
74 SCMOPT_NO_STRIP_TRAILING_LINES,
75 SCMOPT_FIX_FLOWER_BOX_MARKERS,
76 SCMOPT_NO_FIX_FLOWER_BOX_MARKERS,
77 SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS,
78 SCMOPT_ONLY_SVN_DIRS,
79 SCMOPT_NOT_ONLY_SVN_DIRS,
80 SCMOPT_ONLY_SVN_FILES,
81 SCMOPT_NOT_ONLY_SVN_FILES,
82 SCMOPT_SET_SVN_EOL,
83 SCMOPT_DONT_SET_SVN_EOL,
84 SCMOPT_SET_SVN_EXECUTABLE,
85 SCMOPT_DONT_SET_SVN_EXECUTABLE,
86 SCMOPT_SET_SVN_KEYWORDS,
87 SCMOPT_DONT_SET_SVN_KEYWORDS,
88 SCMOPT_TAB_SIZE,
89 SCMOPT_WIDTH,
90 SCMOPT_FILTER_OUT_DIRS,
91 SCMOPT_FILTER_FILES,
92 SCMOPT_FILTER_OUT_FILES,
93 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
94 //
95 SCMOPT_DIFF_IGNORE_EOL,
96 SCMOPT_DIFF_NO_IGNORE_EOL,
97 SCMOPT_DIFF_IGNORE_SPACE,
98 SCMOPT_DIFF_NO_IGNORE_SPACE,
99 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
100 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
101 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
102 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
103 SCMOPT_DIFF_SPECIAL_CHARS,
104 SCMOPT_DIFF_NO_SPECIAL_CHARS,
105 SCMOPT_END
106} SCMOPT;
107
108
109/*********************************************************************************************************************************
110* Global Variables *
111*********************************************************************************************************************************/
112const char g_szTabSpaces[16+1] = " ";
113const char g_szAsterisks[255+1] =
114"****************************************************************************************************"
115"****************************************************************************************************"
116"*******************************************************";
117const char g_szSpaces[255+1] =
118" "
119" "
120" ";
121static const char g_szProgName[] = "scm";
122static const char *g_pszChangedSuff = "";
123static bool g_fDryRun = true;
124static bool g_fDiffSpecialChars = true;
125static bool g_fDiffIgnoreEol = false;
126static bool g_fDiffIgnoreLeadingWS = false;
127static bool g_fDiffIgnoreTrailingWS = false;
128static int g_iVerbosity = 2;//99; //0;
129
130/** The global settings. */
131static SCMSETTINGSBASE const g_Defaults =
132{
133 /* .fConvertEol = */ true,
134 /* .fConvertTabs = */ true,
135 /* .fForceFinalEol = */ true,
136 /* .fForceTrailingLine = */ false,
137 /* .fStripTrailingBlanks = */ true,
138 /* .fStripTrailingLines = */ true,
139 /* .fFixFlowerBoxMarkers = */ true,
140 /* .cMinBlankLinesBeforeFlowerBoxMakers = */ 2,
141 /* .fOnlySvnFiles = */ false,
142 /* .fOnlySvnDirs = */ false,
143 /* .fSetSvnEol = */ false,
144 /* .fSetSvnExecutable = */ false,
145 /* .fSetSvnKeywords = */ false,
146 /* .cchTab = */ 8,
147 /* .cchWidth = */ 130,
148 /* .pszFilterFiles = */ (char *)"",
149 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
150 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
151};
152
153/** Option definitions for the base settings. */
154static RTGETOPTDEF g_aScmOpts[] =
155{
156 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
157 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
158 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
159 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
160 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
161 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
162 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
163 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
164 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
165 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
166 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
167 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
168 { "--min-blank-lines-before-flower-box-makers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_UINT8 },
169 { "--fix-flower-box-markers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
170 { "--no-fix-flower-box-markers", SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
171 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
172 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
173 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
174 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
175 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
176 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
177 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
178 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
179 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
180 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
181 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
182 { "--width", SCMOPT_WIDTH, RTGETOPT_REQ_UINT8 },
183 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
184 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
185 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
186};
187
188/** Consider files matching the following patterns (base names only). */
189static const char *g_pszFileFilter = NULL;
190
191static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
192{
193 rewrite_SvnNoExecutable,
194 rewrite_Makefile_kup
195};
196
197static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
198{
199 rewrite_ForceNativeEol,
200 rewrite_StripTrailingBlanks,
201 rewrite_AdjustTrailingLines,
202 rewrite_SvnNoExecutable,
203 rewrite_SvnKeywords,
204 rewrite_Makefile_kmk
205};
206
207static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
208{
209 rewrite_ForceNativeEol,
210 rewrite_ExpandTabs,
211 rewrite_StripTrailingBlanks,
212 rewrite_AdjustTrailingLines,
213 rewrite_SvnNoExecutable,
214 rewrite_SvnKeywords,
215 rewrite_FixFlowerBoxMarkers,
216 rewrite_C_and_CPP
217};
218
219static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
220{
221 rewrite_ForceNativeEol,
222 rewrite_ExpandTabs,
223 rewrite_StripTrailingBlanks,
224 rewrite_AdjustTrailingLines,
225 rewrite_SvnNoExecutable,
226 rewrite_C_and_CPP
227};
228
229static PFNSCMREWRITER const g_aRewritersFor_RC[] =
230{
231 rewrite_ForceNativeEol,
232 rewrite_ExpandTabs,
233 rewrite_StripTrailingBlanks,
234 rewrite_AdjustTrailingLines,
235 rewrite_SvnNoExecutable,
236 rewrite_SvnKeywords
237};
238
239static PFNSCMREWRITER const g_aRewritersFor_DEF[] =
240{
241 rewrite_ForceNativeEol,
242 rewrite_ExpandTabs,
243 rewrite_StripTrailingBlanks,
244 rewrite_AdjustTrailingLines,
245 rewrite_SvnNoExecutable,
246 rewrite_SvnKeywords
247};
248
249static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
250{
251 rewrite_ForceLF,
252 rewrite_ExpandTabs,
253 rewrite_StripTrailingBlanks
254};
255
256static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
257{
258 rewrite_ForceCRLF,
259 rewrite_ExpandTabs,
260 rewrite_StripTrailingBlanks
261};
262
263static PFNSCMREWRITER const g_aRewritersFor_SedScripts[] =
264{
265 rewrite_ForceLF,
266 rewrite_ExpandTabs,
267 rewrite_StripTrailingBlanks
268};
269
270static PFNSCMREWRITER const g_aRewritersFor_Python[] =
271{
272 /** @todo rewrite_ForceLFIfExecutable */
273 rewrite_ExpandTabs,
274 rewrite_StripTrailingBlanks,
275 rewrite_AdjustTrailingLines,
276 rewrite_SvnKeywords
277};
278
279
280static SCMCFGENTRY const g_aConfigs[] =
281{
282 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
283 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
284 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm" },
285 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },
286 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
287 { RT_ELEMENTS(g_aRewritersFor_DEF), &g_aRewritersFor_DEF[0], "*.def" },
288 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
289 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
290 { RT_ELEMENTS(g_aRewritersFor_SedScripts), &g_aRewritersFor_SedScripts[0], "*.sed" },
291 { RT_ELEMENTS(g_aRewritersFor_Python), &g_aRewritersFor_Python[0], "*.py" },
292};
293
294
295
296/* -=-=-=-=-=- settings -=-=-=-=-=- */
297
298
299/**
300 * Init a settings structure with settings from @a pSrc.
301 *
302 * @returns IPRT status code
303 * @param pSettings The settings.
304 * @param pSrc The source settings.
305 */
306static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
307{
308 *pSettings = *pSrc;
309
310 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
311 if (RT_SUCCESS(rc))
312 {
313 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
314 if (RT_SUCCESS(rc))
315 {
316 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
317 if (RT_SUCCESS(rc))
318 return VINF_SUCCESS;
319
320 RTStrFree(pSettings->pszFilterOutFiles);
321 }
322 RTStrFree(pSettings->pszFilterFiles);
323 }
324
325 pSettings->pszFilterFiles = NULL;
326 pSettings->pszFilterOutFiles = NULL;
327 pSettings->pszFilterOutDirs = NULL;
328 return rc;
329}
330
331/**
332 * Init a settings structure.
333 *
334 * @returns IPRT status code
335 * @param pSettings The settings.
336 */
337static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
338{
339 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
340}
341
342/**
343 * Deletes the settings, i.e. free any dynamically allocated content.
344 *
345 * @param pSettings The settings.
346 */
347static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
348{
349 if (pSettings)
350 {
351 Assert(pSettings->cchTab != UINT8_MAX);
352 pSettings->cchTab = UINT8_MAX;
353
354 RTStrFree(pSettings->pszFilterFiles);
355 pSettings->pszFilterFiles = NULL;
356
357 RTStrFree(pSettings->pszFilterOutFiles);
358 pSettings->pszFilterOutFiles = NULL;
359
360 RTStrFree(pSettings->pszFilterOutDirs);
361 pSettings->pszFilterOutDirs = NULL;
362 }
363}
364
365
366/**
367 * Processes a RTGetOpt result.
368 *
369 * @retval VINF_SUCCESS if handled.
370 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
371 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
372 *
373 * @param pSettings The settings to change.
374 * @param rc The RTGetOpt return value.
375 * @param pValueUnion The RTGetOpt value union.
376 */
377static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
378{
379 switch (rc)
380 {
381 case SCMOPT_CONVERT_EOL:
382 pSettings->fConvertEol = true;
383 return VINF_SUCCESS;
384 case SCMOPT_NO_CONVERT_EOL:
385 pSettings->fConvertEol = false;
386 return VINF_SUCCESS;
387
388 case SCMOPT_CONVERT_TABS:
389 pSettings->fConvertTabs = true;
390 return VINF_SUCCESS;
391 case SCMOPT_NO_CONVERT_TABS:
392 pSettings->fConvertTabs = false;
393 return VINF_SUCCESS;
394
395 case SCMOPT_FORCE_FINAL_EOL:
396 pSettings->fForceFinalEol = true;
397 return VINF_SUCCESS;
398 case SCMOPT_NO_FORCE_FINAL_EOL:
399 pSettings->fForceFinalEol = false;
400 return VINF_SUCCESS;
401
402 case SCMOPT_FORCE_TRAILING_LINE:
403 pSettings->fForceTrailingLine = true;
404 return VINF_SUCCESS;
405 case SCMOPT_NO_FORCE_TRAILING_LINE:
406 pSettings->fForceTrailingLine = false;
407 return VINF_SUCCESS;
408
409
410 case SCMOPT_STRIP_TRAILING_BLANKS:
411 pSettings->fStripTrailingBlanks = true;
412 return VINF_SUCCESS;
413 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
414 pSettings->fStripTrailingBlanks = false;
415 return VINF_SUCCESS;
416
417 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS:
418 pSettings->cMinBlankLinesBeforeFlowerBoxMakers = pValueUnion->u8;
419 return VINF_SUCCESS;
420
421
422 case SCMOPT_STRIP_TRAILING_LINES:
423 pSettings->fStripTrailingLines = true;
424 return VINF_SUCCESS;
425 case SCMOPT_NO_STRIP_TRAILING_LINES:
426 pSettings->fStripTrailingLines = false;
427 return VINF_SUCCESS;
428
429 case SCMOPT_FIX_FLOWER_BOX_MARKERS:
430 pSettings->fFixFlowerBoxMarkers = true;
431 return VINF_SUCCESS;
432 case SCMOPT_NO_FIX_FLOWER_BOX_MARKERS:
433 pSettings->fFixFlowerBoxMarkers = false;
434 return VINF_SUCCESS;
435
436 case SCMOPT_ONLY_SVN_DIRS:
437 pSettings->fOnlySvnDirs = true;
438 return VINF_SUCCESS;
439 case SCMOPT_NOT_ONLY_SVN_DIRS:
440 pSettings->fOnlySvnDirs = false;
441 return VINF_SUCCESS;
442
443 case SCMOPT_ONLY_SVN_FILES:
444 pSettings->fOnlySvnFiles = true;
445 return VINF_SUCCESS;
446 case SCMOPT_NOT_ONLY_SVN_FILES:
447 pSettings->fOnlySvnFiles = false;
448 return VINF_SUCCESS;
449
450 case SCMOPT_SET_SVN_EOL:
451 pSettings->fSetSvnEol = true;
452 return VINF_SUCCESS;
453 case SCMOPT_DONT_SET_SVN_EOL:
454 pSettings->fSetSvnEol = false;
455 return VINF_SUCCESS;
456
457 case SCMOPT_SET_SVN_EXECUTABLE:
458 pSettings->fSetSvnExecutable = true;
459 return VINF_SUCCESS;
460 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
461 pSettings->fSetSvnExecutable = false;
462 return VINF_SUCCESS;
463
464 case SCMOPT_SET_SVN_KEYWORDS:
465 pSettings->fSetSvnKeywords = true;
466 return VINF_SUCCESS;
467 case SCMOPT_DONT_SET_SVN_KEYWORDS:
468 pSettings->fSetSvnKeywords = false;
469 return VINF_SUCCESS;
470
471 case SCMOPT_TAB_SIZE:
472 if ( pValueUnion->u8 < 1
473 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
474 {
475 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
476 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
477 return VERR_OUT_OF_RANGE;
478 }
479 pSettings->cchTab = pValueUnion->u8;
480 return VINF_SUCCESS;
481
482 case SCMOPT_WIDTH:
483 if (pValueUnion->u8 < 20 || pValueUnion->u8 > 200)
484 {
485 RTMsgError("Invalid width size: %u - must be in {20..200} range\n", pValueUnion->u8);
486 return VERR_OUT_OF_RANGE;
487 }
488 pSettings->cchWidth = pValueUnion->u8;
489 return VINF_SUCCESS;
490
491 case SCMOPT_FILTER_OUT_DIRS:
492 case SCMOPT_FILTER_FILES:
493 case SCMOPT_FILTER_OUT_FILES:
494 {
495 char **ppsz = NULL;
496 switch (rc)
497 {
498 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
499 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
500 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
501 }
502
503 /*
504 * An empty string zaps the current list.
505 */
506 if (!*pValueUnion->psz)
507 return RTStrATruncate(ppsz, 0);
508
509 /*
510 * Non-empty strings are appended to the pattern list.
511 *
512 * Strip leading and trailing pattern separators before attempting
513 * to append it. If it's just separators, don't do anything.
514 */
515 const char *pszSrc = pValueUnion->psz;
516 while (*pszSrc == '|')
517 pszSrc++;
518 size_t cchSrc = strlen(pszSrc);
519 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
520 cchSrc--;
521 if (!cchSrc)
522 return VINF_SUCCESS;
523
524 return RTStrAAppendExN(ppsz, 2,
525 "|", *ppsz && **ppsz ? (size_t)1 : (size_t)0,
526 pszSrc, cchSrc);
527 }
528
529 default:
530 return VERR_GETOPT_UNKNOWN_OPTION;
531 }
532}
533
534/**
535 * Parses an option string.
536 *
537 * @returns IPRT status code.
538 * @param pBase The base settings structure to apply the options
539 * to.
540 * @param pszOptions The options to parse.
541 */
542static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
543{
544 int cArgs;
545 char **papszArgs;
546 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
547 if (RT_SUCCESS(rc))
548 {
549 RTGETOPTUNION ValueUnion;
550 RTGETOPTSTATE GetOptState;
551 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
552 if (RT_SUCCESS(rc))
553 {
554 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
555 {
556 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
557 if (RT_FAILURE(rc))
558 break;
559 }
560 }
561 RTGetOptArgvFree(papszArgs);
562 }
563
564 return rc;
565}
566
567/**
568 * Parses an unterminated option string.
569 *
570 * @returns IPRT status code.
571 * @param pBase The base settings structure to apply the options
572 * to.
573 * @param pchLine The line.
574 * @param cchLine The line length.
575 */
576static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
577{
578 char *pszLine = RTStrDupN(pchLine, cchLine);
579 if (!pszLine)
580 return VERR_NO_MEMORY;
581 int rc = scmSettingsBaseParseString(pBase, pszLine);
582 RTStrFree(pszLine);
583 return rc;
584}
585
586/**
587 * Verifies the options string.
588 *
589 * @returns IPRT status code.
590 * @param pszOptions The options to verify .
591 */
592static int scmSettingsBaseVerifyString(const char *pszOptions)
593{
594 SCMSETTINGSBASE Base;
595 int rc = scmSettingsBaseInit(&Base);
596 if (RT_SUCCESS(rc))
597 {
598 rc = scmSettingsBaseParseString(&Base, pszOptions);
599 scmSettingsBaseDelete(&Base);
600 }
601 return rc;
602}
603
604/**
605 * Loads settings found in editor and SCM settings directives within the
606 * document (@a pStream).
607 *
608 * @returns IPRT status code.
609 * @param pBase The settings base to load settings into.
610 * @param pStream The stream to scan for settings directives.
611 */
612static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
613{
614 /** @todo Editor and SCM settings directives in documents. */
615 return VINF_SUCCESS;
616}
617
618/**
619 * Creates a new settings file struct, cloning @a pSettings.
620 *
621 * @returns IPRT status code.
622 * @param ppSettings Where to return the new struct.
623 * @param pSettingsBase The settings to inherit from.
624 */
625static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
626{
627 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
628 if (!pSettings)
629 return VERR_NO_MEMORY;
630 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
631 if (RT_SUCCESS(rc))
632 {
633 pSettings->pDown = NULL;
634 pSettings->pUp = NULL;
635 pSettings->paPairs = NULL;
636 pSettings->cPairs = 0;
637 *ppSettings = pSettings;
638 return VINF_SUCCESS;
639 }
640 RTMemFree(pSettings);
641 return rc;
642}
643
644/**
645 * Destroys a settings structure.
646 *
647 * @param pSettings The settings structure to destroy. NULL is OK.
648 */
649static void scmSettingsDestroy(PSCMSETTINGS pSettings)
650{
651 if (pSettings)
652 {
653 scmSettingsBaseDelete(&pSettings->Base);
654 for (size_t i = 0; i < pSettings->cPairs; i++)
655 {
656 RTStrFree(pSettings->paPairs[i].pszPattern);
657 RTStrFree(pSettings->paPairs[i].pszOptions);
658 pSettings->paPairs[i].pszPattern = NULL;
659 pSettings->paPairs[i].pszOptions = NULL;
660 }
661 RTMemFree(pSettings->paPairs);
662 pSettings->paPairs = NULL;
663 RTMemFree(pSettings);
664 }
665}
666
667/**
668 * Adds a pattern/options pair to the settings structure.
669 *
670 * @returns IPRT status code.
671 * @param pSettings The settings.
672 * @param pchLine The line containing the unparsed pair.
673 * @param cchLine The length of the line.
674 */
675static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
676{
677 /*
678 * Split the string.
679 */
680 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
681 if (!pchOptions)
682 return VERR_INVALID_PARAMETER;
683 size_t cchPattern = pchOptions - pchLine;
684 size_t cchOptions = cchLine - cchPattern - 1;
685 pchOptions++;
686
687 /* strip spaces everywhere */
688 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
689 cchPattern--;
690 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
691 cchPattern--, pchLine++;
692
693 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
694 cchOptions--;
695 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
696 cchOptions--, pchOptions++;
697
698 /* Quietly ignore empty patterns and empty options. */
699 if (!cchOptions || !cchPattern)
700 return VINF_SUCCESS;
701
702 /*
703 * Add the pair and verify the option string.
704 */
705 uint32_t iPair = pSettings->cPairs;
706 if ((iPair % 32) == 0)
707 {
708 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
709 if (!pvNew)
710 return VERR_NO_MEMORY;
711 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
712 }
713
714 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
715 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
716 int rc;
717 if ( pSettings->paPairs[iPair].pszPattern
718 && pSettings->paPairs[iPair].pszOptions)
719 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
720 else
721 rc = VERR_NO_MEMORY;
722 if (RT_SUCCESS(rc))
723 pSettings->cPairs = iPair + 1;
724 else
725 {
726 RTStrFree(pSettings->paPairs[iPair].pszPattern);
727 RTStrFree(pSettings->paPairs[iPair].pszOptions);
728 }
729 return rc;
730}
731
732/**
733 * Loads in the settings from @a pszFilename.
734 *
735 * @returns IPRT status code.
736 * @param pSettings Where to load the settings file.
737 * @param pszFilename The file to load.
738 */
739static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
740{
741 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
742
743 SCMSTREAM Stream;
744 int rc = ScmStreamInitForReading(&Stream, pszFilename);
745 if (RT_FAILURE(rc))
746 {
747 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
748 return rc;
749 }
750
751 SCMEOL enmEol;
752 const char *pchLine;
753 size_t cchLine;
754 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
755 {
756 /* Ignore leading spaces. */
757 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
758 pchLine++, cchLine--;
759
760 /* Ignore empty lines and comment lines. */
761 if (cchLine < 1 || *pchLine == '#')
762 continue;
763
764 /* What kind of line is it? */
765 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
766 if (pchColon)
767 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
768 else
769 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
770 if (RT_FAILURE(rc))
771 {
772 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
773 break;
774 }
775 }
776
777 if (RT_SUCCESS(rc))
778 {
779 rc = ScmStreamGetStatus(&Stream);
780 if (RT_FAILURE(rc))
781 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
782 }
783
784 ScmStreamDelete(&Stream);
785 return rc;
786}
787
788/**
789 * Parse the specified settings file creating a new settings struct from it.
790 *
791 * @returns IPRT status code
792 * @param ppSettings Where to return the new settings.
793 * @param pszFilename The file to parse.
794 * @param pSettingsBase The base settings we inherit from.
795 */
796static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
797{
798 PSCMSETTINGS pSettings;
799 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
800 if (RT_SUCCESS(rc))
801 {
802 rc = scmSettingsLoadFile(pSettings, pszFilename);
803 if (RT_SUCCESS(rc))
804 {
805 *ppSettings = pSettings;
806 return VINF_SUCCESS;
807 }
808
809 scmSettingsDestroy(pSettings);
810 }
811 *ppSettings = NULL;
812 return rc;
813}
814
815
816/**
817 * Create an initial settings structure when starting processing a new file or
818 * directory.
819 *
820 * This will look for .scm-settings files from the root and down to the
821 * specified directory, combining them into the returned settings structure.
822 *
823 * @returns IPRT status code.
824 * @param ppSettings Where to return the pointer to the top stack
825 * object.
826 * @param pBaseSettings The base settings we inherit from (globals
827 * typically).
828 * @param pszPath The absolute path to the new directory or file.
829 */
830static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
831{
832 *ppSettings = NULL; /* try shut up gcc. */
833
834 /*
835 * We'll be working with a stack copy of the path.
836 */
837 char szFile[RTPATH_MAX];
838 size_t cchDir = strlen(pszPath);
839 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
840 return VERR_FILENAME_TOO_LONG;
841
842 /*
843 * Create the bottom-most settings.
844 */
845 PSCMSETTINGS pSettings;
846 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
847 if (RT_FAILURE(rc))
848 return rc;
849
850 /*
851 * Enumerate the path components from the root and down. Load any setting
852 * files we find.
853 */
854 size_t cComponents = RTPathCountComponents(pszPath);
855 for (size_t i = 1; i <= cComponents; i++)
856 {
857 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
858 if (RT_SUCCESS(rc))
859 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
860 if (RT_FAILURE(rc))
861 break;
862 if (RTFileExists(szFile))
863 {
864 rc = scmSettingsLoadFile(pSettings, szFile);
865 if (RT_FAILURE(rc))
866 break;
867 }
868 }
869
870 if (RT_SUCCESS(rc))
871 *ppSettings = pSettings;
872 else
873 scmSettingsDestroy(pSettings);
874 return rc;
875}
876
877/**
878 * Pushes a new settings set onto the stack.
879 *
880 * @param ppSettingsStack The pointer to the pointer to the top stack
881 * element. This will be used as input and output.
882 * @param pSettings The settings to push onto the stack.
883 */
884static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
885{
886 PSCMSETTINGS pOld = *ppSettingsStack;
887 pSettings->pDown = pOld;
888 pSettings->pUp = NULL;
889 if (pOld)
890 pOld->pUp = pSettings;
891 *ppSettingsStack = pSettings;
892}
893
894/**
895 * Pushes the settings of the specified directory onto the stack.
896 *
897 * We will load any .scm-settings in the directory. A stack entry is added even
898 * if no settings file was found.
899 *
900 * @returns IPRT status code.
901 * @param ppSettingsStack The pointer to the pointer to the top stack
902 * element. This will be used as input and output.
903 * @param pszDir The directory to do this for.
904 */
905static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
906{
907 char szFile[RTPATH_MAX];
908 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
909 if (RT_SUCCESS(rc))
910 {
911 PSCMSETTINGS pSettings;
912 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
913 if (RT_SUCCESS(rc))
914 {
915 if (RTFileExists(szFile))
916 rc = scmSettingsLoadFile(pSettings, szFile);
917 if (RT_SUCCESS(rc))
918 {
919 scmSettingsStackPush(ppSettingsStack, pSettings);
920 return VINF_SUCCESS;
921 }
922
923 scmSettingsDestroy(pSettings);
924 }
925 }
926 return rc;
927}
928
929
930/**
931 * Pops a settings set off the stack.
932 *
933 * @returns The popped setttings.
934 * @param ppSettingsStack The pointer to the pointer to the top stack
935 * element. This will be used as input and output.
936 */
937static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
938{
939 PSCMSETTINGS pRet = *ppSettingsStack;
940 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
941 *ppSettingsStack = pNew;
942 if (pNew)
943 pNew->pUp = NULL;
944 if (pRet)
945 {
946 pRet->pUp = NULL;
947 pRet->pDown = NULL;
948 }
949 return pRet;
950}
951
952/**
953 * Pops and destroys the top entry of the stack.
954 *
955 * @param ppSettingsStack The pointer to the pointer to the top stack
956 * element. This will be used as input and output.
957 */
958static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
959{
960 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
961}
962
963/**
964 * Constructs the base settings for the specified file name.
965 *
966 * @returns IPRT status code.
967 * @param pSettingsStack The top element on the settings stack.
968 * @param pszFilename The file name.
969 * @param pszBasename The base name (pointer within @a pszFilename).
970 * @param cchBasename The length of the base name. (For passing to
971 * RTStrSimplePatternMultiMatch.)
972 * @param pBase Base settings to initialize.
973 */
974static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
975 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
976{
977 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
978 if (RT_SUCCESS(rc))
979 {
980 /* find the bottom entry in the stack. */
981 PCSCMSETTINGS pCur = pSettingsStack;
982 while (pCur->pDown)
983 pCur = pCur->pDown;
984
985 /* Work our way up thru the stack and look for matching pairs. */
986 while (pCur)
987 {
988 size_t const cPairs = pCur->cPairs;
989 if (cPairs)
990 {
991 for (size_t i = 0; i < cPairs; i++)
992 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
993 pszBasename, cchBasename, NULL)
994 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
995 pszFilename, RTSTR_MAX, NULL))
996 {
997 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
998 if (RT_FAILURE(rc))
999 break;
1000 }
1001 if (RT_FAILURE(rc))
1002 break;
1003 }
1004
1005 /* advance */
1006 pCur = pCur->pUp;
1007 }
1008 }
1009 if (RT_FAILURE(rc))
1010 scmSettingsBaseDelete(pBase);
1011 return rc;
1012}
1013
1014
1015/* -=-=-=-=-=- misc -=-=-=-=-=- */
1016
1017
1018/**
1019 * Prints a verbose message if the level is high enough.
1020 *
1021 * @param pState The rewrite state. Optional.
1022 * @param iLevel The required verbosity level.
1023 * @param pszFormat The message format string. Can be NULL if we
1024 * only want to trigger the per file message.
1025 * @param ... Format arguments.
1026 */
1027void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
1028{
1029 if (iLevel <= g_iVerbosity)
1030 {
1031 if (pState && !pState->fFirst)
1032 {
1033 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1034 pState->fFirst = true;
1035 }
1036 if (pszFormat)
1037 {
1038 RTPrintf(pState
1039 ? "%s: info: "
1040 : "%s: info: ",
1041 g_szProgName);
1042 va_list va;
1043 va_start(va, pszFormat);
1044 RTPrintfV(pszFormat, va);
1045 va_end(va);
1046 }
1047 }
1048}
1049
1050
1051/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
1052
1053
1054/**
1055 * Processes a file.
1056 *
1057 * @returns IPRT status code.
1058 * @param pState The rewriter state.
1059 * @param pszFilename The file name.
1060 * @param pszBasename The base name (pointer within @a pszFilename).
1061 * @param cchBasename The length of the base name. (For passing to
1062 * RTStrSimplePatternMultiMatch.)
1063 * @param pBaseSettings The base settings to use. It's OK to modify
1064 * these.
1065 */
1066static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
1067 PSCMSETTINGSBASE pBaseSettings)
1068{
1069 /*
1070 * Do the file level filtering.
1071 */
1072 if ( pBaseSettings->pszFilterFiles
1073 && *pBaseSettings->pszFilterFiles
1074 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
1075 {
1076 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
1077 return VINF_SUCCESS;
1078 }
1079 if ( pBaseSettings->pszFilterOutFiles
1080 && *pBaseSettings->pszFilterOutFiles
1081 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
1082 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
1083 {
1084 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
1085 return VINF_SUCCESS;
1086 }
1087 if ( pBaseSettings->fOnlySvnFiles
1088 && !ScmSvnIsInWorkingCopy(pState))
1089 {
1090 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
1091 return VINF_SUCCESS;
1092 }
1093
1094 /*
1095 * Try find a matching rewrite config for this filename.
1096 */
1097 PCSCMCFGENTRY pCfg = NULL;
1098 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1099 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
1100 {
1101 pCfg = &g_aConfigs[iCfg];
1102 break;
1103 }
1104 if (!pCfg)
1105 {
1106 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
1107 return VINF_SUCCESS;
1108 }
1109 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
1110
1111 /*
1112 * Create an input stream from the file and check that it's text.
1113 */
1114 SCMSTREAM Stream1;
1115 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
1116 if (RT_FAILURE(rc))
1117 {
1118 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
1119 return rc;
1120 }
1121 if (ScmStreamIsText(&Stream1))
1122 {
1123 ScmVerbose(pState, 3, NULL);
1124
1125 /*
1126 * Gather SCM and editor settings from the stream.
1127 */
1128 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
1129 if (RT_SUCCESS(rc))
1130 {
1131 ScmStreamRewindForReading(&Stream1);
1132
1133 /*
1134 * Create two more streams for output and push the text thru all the
1135 * rewriters, switching the two streams around when something is
1136 * actually rewritten. Stream1 remains unchanged.
1137 */
1138 SCMSTREAM Stream2;
1139 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
1140 if (RT_SUCCESS(rc))
1141 {
1142 SCMSTREAM Stream3;
1143 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
1144 if (RT_SUCCESS(rc))
1145 {
1146 bool fModified = false;
1147 PSCMSTREAM pIn = &Stream1;
1148 PSCMSTREAM pOut = &Stream2;
1149 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
1150 {
1151 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
1152 if (fRc)
1153 {
1154 PSCMSTREAM pTmp = pOut;
1155 pOut = pIn == &Stream1 ? &Stream3 : pIn;
1156 pIn = pTmp;
1157 fModified = true;
1158 }
1159 ScmStreamRewindForReading(pIn);
1160 ScmStreamRewindForWriting(pOut);
1161 }
1162
1163 rc = ScmStreamGetStatus(&Stream1);
1164 if (RT_SUCCESS(rc))
1165 rc = ScmStreamGetStatus(&Stream2);
1166 if (RT_SUCCESS(rc))
1167 rc = ScmStreamGetStatus(&Stream3);
1168 if (RT_SUCCESS(rc))
1169 {
1170 /*
1171 * If rewritten, write it back to disk.
1172 */
1173 if (fModified)
1174 {
1175 if (!g_fDryRun)
1176 {
1177 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1178 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
1179 if (RT_FAILURE(rc))
1180 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
1181 }
1182 else
1183 {
1184 ScmVerbose(pState, 1, NULL);
1185 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
1186 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
1187 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1188 }
1189 }
1190
1191 /*
1192 * If pending SVN property changes, apply them.
1193 */
1194 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
1195 {
1196 if (!g_fDryRun)
1197 {
1198 rc = ScmSvnApplyChanges(pState);
1199 if (RT_FAILURE(rc))
1200 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
1201 }
1202 else
1203 ScmSvnDisplayChanges(pState);
1204 }
1205
1206 if (!fModified && !pState->cSvnPropChanges)
1207 ScmVerbose(pState, 3, "no change\n", pszFilename);
1208 }
1209 else
1210 RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
1211 ScmStreamDelete(&Stream3);
1212 }
1213 else
1214 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1215 ScmStreamDelete(&Stream2);
1216 }
1217 else
1218 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1219 }
1220 else
1221 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
1222 }
1223 else
1224 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
1225 ScmStreamDelete(&Stream1);
1226
1227 return rc;
1228}
1229
1230/**
1231 * Processes a file.
1232 *
1233 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
1234 * directory recursion method.
1235 *
1236 * @returns IPRT status code.
1237 * @param pszFilename The file name.
1238 * @param pszBasename The base name (pointer within @a pszFilename).
1239 * @param cchBasename The length of the base name. (For passing to
1240 * RTStrSimplePatternMultiMatch.)
1241 * @param pSettingsStack The settings stack (pointer to the top element).
1242 */
1243static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
1244 PSCMSETTINGS pSettingsStack)
1245{
1246 SCMSETTINGSBASE Base;
1247 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
1248 if (RT_SUCCESS(rc))
1249 {
1250 SCMRWSTATE State;
1251 State.fFirst = false;
1252 State.pszFilename = pszFilename;
1253 State.cSvnPropChanges = 0;
1254 State.paSvnPropChanges = NULL;
1255
1256 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
1257
1258 size_t i = State.cSvnPropChanges;
1259 while (i-- > 0)
1260 {
1261 RTStrFree(State.paSvnPropChanges[i].pszName);
1262 RTStrFree(State.paSvnPropChanges[i].pszValue);
1263 }
1264 RTMemFree(State.paSvnPropChanges);
1265
1266 scmSettingsBaseDelete(&Base);
1267 }
1268 return rc;
1269}
1270
1271
1272/**
1273 * Tries to correct RTDIRENTRY_UNKNOWN.
1274 *
1275 * @returns Corrected type.
1276 * @param pszPath The path to the object in question.
1277 */
1278static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
1279{
1280 RTFSOBJINFO Info;
1281 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
1282 if (RT_FAILURE(rc))
1283 return RTDIRENTRYTYPE_UNKNOWN;
1284 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
1285 return RTDIRENTRYTYPE_DIRECTORY;
1286 if (RTFS_IS_FILE(Info.Attr.fMode))
1287 return RTDIRENTRYTYPE_FILE;
1288 return RTDIRENTRYTYPE_UNKNOWN;
1289}
1290
1291/**
1292 * Recurse into a sub-directory and process all the files and directories.
1293 *
1294 * @returns IPRT status code.
1295 * @param pszBuf Path buffer containing the directory path on
1296 * entry. This ends with a dot. This is passed
1297 * along when recursing in order to save stack space
1298 * and avoid needless copying.
1299 * @param cchDir Length of our path in pszbuf.
1300 * @param pEntry Directory entry buffer. This is also passed
1301 * along when recursing to save stack space.
1302 * @param pSettingsStack The settings stack (pointer to the top element).
1303 * @param iRecursion The recursion depth. This is used to restrict
1304 * the recursions.
1305 */
1306static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
1307 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
1308{
1309 int rc;
1310 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
1311
1312 /*
1313 * Make sure we stop somewhere.
1314 */
1315 if (iRecursion > 128)
1316 {
1317 RTMsgError("recursion too deep: %d\n", iRecursion);
1318 return VINF_SUCCESS; /* ignore */
1319 }
1320
1321 /*
1322 * Check if it's excluded by --only-svn-dir.
1323 */
1324 if (pSettingsStack->Base.fOnlySvnDirs)
1325 {
1326 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
1327 return VINF_SUCCESS;
1328 }
1329
1330 /*
1331 * Try open and read the directory.
1332 */
1333 PRTDIR pDir;
1334 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
1335 if (RT_FAILURE(rc))
1336 {
1337 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
1338 return rc;
1339 }
1340 for (;;)
1341 {
1342 /* Read the next entry. */
1343 rc = RTDirRead(pDir, pEntry, NULL);
1344 if (RT_FAILURE(rc))
1345 {
1346 if (rc == VERR_NO_MORE_FILES)
1347 rc = VINF_SUCCESS;
1348 else
1349 RTMsgError("RTDirRead -> %Rrc\n", rc);
1350 break;
1351 }
1352
1353 /* Skip '.' and '..'. */
1354 if ( pEntry->szName[0] == '.'
1355 && ( pEntry->cbName == 1
1356 || ( pEntry->cbName == 2
1357 && pEntry->szName[1] == '.')))
1358 continue;
1359
1360 /* Enter it into the buffer so we've got a full name to work
1361 with when needed. */
1362 if (pEntry->cbName + cchDir >= RTPATH_MAX)
1363 {
1364 RTMsgError("Skipping too long entry: %s", pEntry->szName);
1365 continue;
1366 }
1367 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
1368
1369 /* Figure the type. */
1370 RTDIRENTRYTYPE enmType = pEntry->enmType;
1371 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
1372 enmType = scmFigureUnknownType(pszBuf);
1373
1374 /* Process the file or directory, skip the rest. */
1375 if (enmType == RTDIRENTRYTYPE_FILE)
1376 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
1377 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
1378 {
1379 /* Append the dot for the benefit of the pattern matching. */
1380 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
1381 {
1382 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
1383 continue;
1384 }
1385 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
1386 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
1387
1388 if ( !pSettingsStack->Base.pszFilterOutDirs
1389 || !*pSettingsStack->Base.pszFilterOutDirs
1390 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1391 pEntry->szName, pEntry->cbName, NULL)
1392 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1393 pszBuf, cchSubDir, NULL)
1394 )
1395 )
1396 {
1397 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
1398 if (RT_SUCCESS(rc))
1399 {
1400 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
1401 scmSettingsStackPopAndDestroy(&pSettingsStack);
1402 }
1403 }
1404 }
1405 if (RT_FAILURE(rc))
1406 break;
1407 }
1408 RTDirClose(pDir);
1409 return rc;
1410
1411}
1412
1413/**
1414 * Process a directory tree.
1415 *
1416 * @returns IPRT status code.
1417 * @param pszDir The directory to start with. This is pointer to
1418 * a RTPATH_MAX sized buffer.
1419 */
1420static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
1421{
1422 /*
1423 * Setup the recursion.
1424 */
1425 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
1426 if (RT_SUCCESS(rc))
1427 {
1428 RTDIRENTRY Entry;
1429 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
1430 }
1431 else
1432 RTMsgError("RTPathAppend: %Rrc\n", rc);
1433 return rc;
1434}
1435
1436
1437/**
1438 * Processes a file or directory specified as an command line argument.
1439 *
1440 * @returns IPRT status code
1441 * @param pszSomething What we found in the command line arguments.
1442 * @param pSettingsStack The settings stack (pointer to the top element).
1443 */
1444static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
1445{
1446 char szBuf[RTPATH_MAX];
1447 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
1448 if (RT_SUCCESS(rc))
1449 {
1450 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
1451
1452 PSCMSETTINGS pSettings;
1453 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
1454 if (RT_SUCCESS(rc))
1455 {
1456 scmSettingsStackPush(&pSettingsStack, pSettings);
1457
1458 if (RTFileExists(szBuf))
1459 {
1460 const char *pszBasename = RTPathFilename(szBuf);
1461 if (pszBasename)
1462 {
1463 size_t cchBasename = strlen(pszBasename);
1464 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
1465 }
1466 else
1467 {
1468 RTMsgError("RTPathFilename: NULL\n");
1469 rc = VERR_IS_A_DIRECTORY;
1470 }
1471 }
1472 else
1473 rc = scmProcessDirTree(szBuf, pSettingsStack);
1474
1475 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
1476 Assert(pPopped == pSettings);
1477 scmSettingsDestroy(pSettings);
1478 }
1479 else
1480 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
1481 }
1482 else
1483 RTMsgError("RTPathAbs: %Rrc\n", rc);
1484 return rc;
1485}
1486
1487static void usage(PCRTGETOPTDEF paOpts, size_t cOpts)
1488{
1489 RTPrintf("VirtualBox Source Code Massager\n"
1490 "\n"
1491 "Usage: %s [options] <files & dirs>\n"
1492 "\n"
1493 "Options:\n", g_szProgName);
1494 for (size_t i = 0; i < cOpts; i++)
1495 {
1496 bool fAdvanceTwo = false;
1497 if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
1498 {
1499 fAdvanceTwo = i + 1 < cOpts
1500 && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL
1501 || strstr(paOpts[i+1].pszLong, "-not-") != NULL
1502 || strstr(paOpts[i+1].pszLong, "-dont-") != NULL
1503 || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v')
1504 || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D')
1505 );
1506 if (fAdvanceTwo)
1507 RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong);
1508 else
1509 RTPrintf(" %s\n", paOpts[i].pszLong);
1510 }
1511 else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
1512 RTPrintf(" %s string\n", paOpts[i].pszLong);
1513 else
1514 RTPrintf(" %s value\n", paOpts[i].pszLong);
1515 switch (paOpts[i].iShort)
1516 {
1517 case 'd':
1518 case 'D': RTPrintf(" Default: --dry-run\n"); break;
1519 case 'f': RTPrintf(" Default: none\n"); break;
1520 case 'q':
1521 case 'v': RTPrintf(" Default: -vv\n"); break;
1522
1523 case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break;
1524 case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break;
1525 case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break;
1526 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break;
1527 case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break;
1528
1529 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
1530 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
1531 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
1532 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
1533 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
1534 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
1535 case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break;
1536 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break;
1537 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
1538 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
1539 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
1540 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
1541 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
1542 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
1543 case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break;
1544 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
1545 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
1546 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
1547 default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong));
1548 }
1549 i += fAdvanceTwo;
1550 }
1551
1552}
1553
1554int main(int argc, char **argv)
1555{
1556 int rc = RTR3InitExe(argc, &argv, 0);
1557 if (RT_FAILURE(rc))
1558 return 1;
1559
1560 /*
1561 * Init the settings.
1562 */
1563 PSCMSETTINGS pSettings;
1564 rc = scmSettingsCreate(&pSettings, &g_Defaults);
1565 if (RT_FAILURE(rc))
1566 {
1567 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
1568 return 1;
1569 }
1570
1571 /*
1572 * Parse arguments and process input in order (because this is the only
1573 * thing that works at the moment).
1574 */
1575 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
1576 {
1577 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
1578 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
1579 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
1580 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
1581 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1582 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1583 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1584 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1585 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1586 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1587 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1588 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1589 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1590 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1591 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1592 };
1593 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
1594
1595 RTGETOPTUNION ValueUnion;
1596 RTGETOPTSTATE GetOptState;
1597 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1598 AssertReleaseRCReturn(rc, 1);
1599 size_t cProcessed = 0;
1600
1601 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1602 {
1603 switch (rc)
1604 {
1605 case 'd':
1606 g_fDryRun = true;
1607 break;
1608 case 'D':
1609 g_fDryRun = false;
1610 break;
1611
1612 case 'f':
1613 g_pszFileFilter = ValueUnion.psz;
1614 break;
1615
1616 case 'h':
1617 usage(s_aOpts, RT_ELEMENTS(s_aOpts));
1618 return 1;
1619
1620 case 'q':
1621 g_iVerbosity = 0;
1622 break;
1623
1624 case 'v':
1625 g_iVerbosity++;
1626 break;
1627
1628 case 'V':
1629 {
1630 /* The following is assuming that svn does it's job here. */
1631 static const char s_szRev[] = "$Revision: 57369 $";
1632 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
1633 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
1634 return 0;
1635 }
1636
1637 case SCMOPT_DIFF_IGNORE_EOL:
1638 g_fDiffIgnoreEol = true;
1639 break;
1640 case SCMOPT_DIFF_NO_IGNORE_EOL:
1641 g_fDiffIgnoreEol = false;
1642 break;
1643
1644 case SCMOPT_DIFF_IGNORE_SPACE:
1645 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
1646 break;
1647 case SCMOPT_DIFF_NO_IGNORE_SPACE:
1648 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
1649 break;
1650
1651 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
1652 g_fDiffIgnoreLeadingWS = true;
1653 break;
1654 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
1655 g_fDiffIgnoreLeadingWS = false;
1656 break;
1657
1658 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
1659 g_fDiffIgnoreTrailingWS = true;
1660 break;
1661 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
1662 g_fDiffIgnoreTrailingWS = false;
1663 break;
1664
1665 case SCMOPT_DIFF_SPECIAL_CHARS:
1666 g_fDiffSpecialChars = true;
1667 break;
1668 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
1669 g_fDiffSpecialChars = false;
1670 break;
1671
1672 case VINF_GETOPT_NOT_OPTION:
1673 {
1674 if (!g_fDryRun)
1675 {
1676 if (!cProcessed)
1677 {
1678 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
1679 "%s: there is a slight risk that bugs or a full disk may cause\n"
1680 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
1681 "%s: all your changes already. If you didn't, then don't blame\n"
1682 "%s: anyone for not warning you!\n"
1683 "%s:\n"
1684 "%s: Press any key to continue...\n",
1685 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
1686 g_szProgName, g_szProgName);
1687 RTStrmGetCh(g_pStdIn);
1688 }
1689 cProcessed++;
1690 }
1691 rc = scmProcessSomething(ValueUnion.psz, pSettings);
1692 if (RT_FAILURE(rc))
1693 return rc;
1694 break;
1695 }
1696
1697 default:
1698 {
1699 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
1700 if (RT_SUCCESS(rc2))
1701 break;
1702 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
1703 return 2;
1704 return RTGetOptPrintError(rc, &ValueUnion);
1705 }
1706 }
1707 }
1708
1709 scmSettingsDestroy(pSettings);
1710 return 0;
1711}
1712
Note: See TracBrowser for help on using the repository browser.

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