VirtualBox

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

Last change on this file since 69198 was 69176, checked in by vboxsync, 7 years ago

scmrw.cpp: You'd thought there would only be one MIT license wording in the tree. sigh3

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