VirtualBox

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

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

scm: File/dir patterns in .scm-settings file are considered relative to the settings file location if they start with a slash. This will make it easier to write clear configuration. Also added rewriters for assembly files and scm settings files.

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