VirtualBox

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

Last change on this file since 76551 was 76551, checked in by vboxsync, 6 years ago

scm: More work on header guard massaging (not active yet).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 114.7 KB
Line 
1/* $Id: scm.cpp 76551 2018-12-31 05:09:06Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2017 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_HEADER_GUARDS,
78 SCMOPT_NO_FIX_HEADER_GUARDS,
79 SCMOPT_PRAGMA_ONCE,
80 SCMOPT_NO_PRAGMA_ONCE,
81 SCMOPT_ENDIF_GUARD_COMMENT,
82 SCMOPT_NO_ENDIF_GUARD_COMMENT,
83 SCMOPT_GUARD_PREFIX,
84 SCMOPT_GUARD_RELATIVE_TO_DIR,
85 SCMOPT_FIX_TODOS,
86 SCMOPT_NO_FIX_TODOS,
87 SCMOPT_FIX_ERR_H,
88 SCMOPT_NO_FIX_ERR_H,
89 SCMOPT_UPDATE_COPYRIGHT_YEAR,
90 SCMOPT_NO_UPDATE_COPYRIGHT_YEAR,
91 SCMOPT_EXTERNAL_COPYRIGHT,
92 SCMOPT_NO_EXTERNAL_COPYRIGHT,
93 SCMOPT_NO_UPDATE_LICENSE,
94 SCMOPT_LICENSE_OSE_GPL,
95 SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL,
96 SCMOPT_LICENSE_OSE_CDDL,
97 SCMOPT_LICENSE_LGPL,
98 SCMOPT_LICENSE_MIT,
99 SCMOPT_LICENSE_BASED_ON_MIT,
100 SCMOPT_LGPL_DISCLAIMER,
101 SCMOPT_NO_LGPL_DISCLAIMER,
102 SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS,
103 SCMOPT_ONLY_SVN_DIRS,
104 SCMOPT_NOT_ONLY_SVN_DIRS,
105 SCMOPT_ONLY_SVN_FILES,
106 SCMOPT_NOT_ONLY_SVN_FILES,
107 SCMOPT_SET_SVN_EOL,
108 SCMOPT_DONT_SET_SVN_EOL,
109 SCMOPT_SET_SVN_EXECUTABLE,
110 SCMOPT_DONT_SET_SVN_EXECUTABLE,
111 SCMOPT_SET_SVN_KEYWORDS,
112 SCMOPT_DONT_SET_SVN_KEYWORDS,
113 SCMOPT_SKIP_SVN_SYNC_PROCESS,
114 SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS,
115 SCMOPT_TAB_SIZE,
116 SCMOPT_WIDTH,
117 SCMOPT_FILTER_OUT_DIRS,
118 SCMOPT_FILTER_FILES,
119 SCMOPT_FILTER_OUT_FILES,
120 SCMOPT_TREAT_AS,
121 SCMOPT_ADD_ACTION,
122 SCMOPT_DEL_ACTION,
123 SCMOPT_LAST_SETTINGS = SCMOPT_DEL_ACTION,
124 //
125 SCMOPT_CHECK_RUN,
126 SCMOPT_DIFF_IGNORE_EOL,
127 SCMOPT_DIFF_NO_IGNORE_EOL,
128 SCMOPT_DIFF_IGNORE_SPACE,
129 SCMOPT_DIFF_NO_IGNORE_SPACE,
130 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
131 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
132 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
133 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
134 SCMOPT_DIFF_SPECIAL_CHARS,
135 SCMOPT_DIFF_NO_SPECIAL_CHARS,
136 SCMOPT_HELP_CONFIG,
137 SCMOPT_HELP_ACTIONS,
138 SCMOPT_END
139} SCMOPT;
140
141
142/*********************************************************************************************************************************
143* Global Variables *
144*********************************************************************************************************************************/
145const char g_szTabSpaces[16+1] = " ";
146const char g_szAsterisks[255+1] =
147"****************************************************************************************************"
148"****************************************************************************************************"
149"*******************************************************";
150const char g_szSpaces[255+1] =
151" "
152" "
153" ";
154static const char g_szProgName[] = "scm";
155static const char *g_pszChangedSuff = "";
156static bool g_fDryRun = true;
157static bool g_fDiffSpecialChars = true;
158static bool g_fDiffIgnoreEol = false;
159static bool g_fDiffIgnoreLeadingWS = false;
160static bool g_fDiffIgnoreTrailingWS = false;
161static int g_iVerbosity = 2;//99; //0;
162uint32_t g_uYear = 0; /**< The current year. */
163/** @name Statistics
164 * @{ */
165static uint32_t g_cDirsProcessed = 0;
166static uint32_t g_cFilesProcessed = 0;
167static uint32_t g_cFilesModified = 0;
168static uint32_t g_cFilesSkipped = 0;
169static uint32_t g_cFilesNotInSvn = 0;
170static uint32_t g_cFilesNoRewriters = 0;
171static uint32_t g_cFilesBinaries = 0;
172/** @} */
173
174/** The global settings. */
175static SCMSETTINGSBASE const g_Defaults =
176{
177 /* .fConvertEol = */ true,
178 /* .fConvertTabs = */ true,
179 /* .fForceFinalEol = */ true,
180 /* .fForceTrailingLine = */ false,
181 /* .fStripTrailingBlanks = */ true,
182 /* .fStripTrailingLines = */ true,
183 /* .fFixFlowerBoxMarkers = */ true,
184 /* .cMinBlankLinesBeforeFlowerBoxMakers = */ 2,
185 /* .fFixHeaderGuards = */ true,
186 /* .fPragmaOnce = */ true,
187 /* .fEndifGuardComment = */ true,
188 /* .pszGuardPrefix = */ (char *)"VBOX_INCLUDED_",
189 /* .pszGuardRelativeToDir = */ NULL,
190 /* .fFixTodos = */ true,
191 /* .fFixErrH = */ true,
192 /* .fUpdateCopyrightYear = */ false,
193 /* .fExternalCopyright = */ false,
194 /* .fLgplDisclaimer = */ false,
195 /* .enmUpdateLicense = */ kScmLicense_OseGpl,
196 /* .fOnlySvnFiles = */ false,
197 /* .fOnlySvnDirs = */ false,
198 /* .fSetSvnEol = */ false,
199 /* .fSetSvnExecutable = */ false,
200 /* .fSetSvnKeywords = */ false,
201 /* .fSkipSvnSyncProcess = */ false,
202 /* .cchTab = */ 8,
203 /* .cchWidth = */ 130,
204 /* .fFreeTreatAs = */ false,
205 /* .pTreatAs = */ NULL,
206 /* .pszFilterFiles = */ (char *)"",
207 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
208 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
209};
210
211/** Option definitions for the base settings. */
212static RTGETOPTDEF g_aScmOpts[] =
213{
214 /* rewriters */
215 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
216 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
217 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
218 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
219 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
220 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
221 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
222 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
223 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
224 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
225 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
226 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
227 { "--min-blank-lines-before-flower-box-makers", SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS, RTGETOPT_REQ_UINT8 },
228 { "--fix-flower-box-markers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
229 { "--no-fix-flower-box-markers", SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
230 { "--fix-header-guards", SCMOPT_FIX_HEADER_GUARDS, RTGETOPT_REQ_NOTHING },
231 { "--no-fix-header-guards", SCMOPT_NO_FIX_HEADER_GUARDS, RTGETOPT_REQ_NOTHING },
232 { "--pragma-once", SCMOPT_PRAGMA_ONCE, RTGETOPT_REQ_NOTHING },
233 { "--no-pragma-once", SCMOPT_NO_PRAGMA_ONCE, RTGETOPT_REQ_NOTHING },
234 { "--endif-guard-comment", SCMOPT_ENDIF_GUARD_COMMENT, RTGETOPT_REQ_NOTHING },
235 { "--no-endif-guard-comment", SCMOPT_NO_ENDIF_GUARD_COMMENT, RTGETOPT_REQ_NOTHING },
236 { "--guard-prefix", SCMOPT_GUARD_PREFIX, RTGETOPT_REQ_STRING },
237 { "--guard-relative-to-dir", SCMOPT_GUARD_RELATIVE_TO_DIR, RTGETOPT_REQ_STRING },
238 { "--fix-todos", SCMOPT_FIX_TODOS, RTGETOPT_REQ_NOTHING },
239 { "--no-fix-todos", SCMOPT_NO_FIX_TODOS, RTGETOPT_REQ_NOTHING },
240 { "--fix-err-h", SCMOPT_FIX_ERR_H, RTGETOPT_REQ_NOTHING },
241 { "--no-fix-err-h", SCMOPT_NO_FIX_ERR_H, RTGETOPT_REQ_NOTHING },
242 { "--update-copyright-year", SCMOPT_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
243 { "--no-update-copyright-year", SCMOPT_NO_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
244 { "--external-copyright", SCMOPT_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
245 { "--no-external-copyright", SCMOPT_NO_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
246 { "--no-update-license", SCMOPT_NO_UPDATE_LICENSE, RTGETOPT_REQ_NOTHING },
247 { "--license-ose-gpl", SCMOPT_LICENSE_OSE_GPL, RTGETOPT_REQ_NOTHING },
248 { "--license-ose-dual", SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL, RTGETOPT_REQ_NOTHING },
249 { "--license-ose-cddl", SCMOPT_LICENSE_OSE_CDDL, RTGETOPT_REQ_NOTHING },
250 { "--license-lgpl", SCMOPT_LICENSE_LGPL, RTGETOPT_REQ_NOTHING },
251 { "--license-mit", SCMOPT_LICENSE_MIT, RTGETOPT_REQ_NOTHING },
252 { "--license-based-on-mit", SCMOPT_LICENSE_BASED_ON_MIT, RTGETOPT_REQ_NOTHING },
253 { "--lgpl-disclaimer", SCMOPT_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING },
254 { "--no-lgpl-disclaimer", SCMOPT_NO_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING },
255 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
256 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
257 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
258 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
259 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
260 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
261 { "--skip-svn-sync-process", SCMOPT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING },
262 { "--dont-skip-svn-sync-process", SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING },
263 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
264 { "--width", SCMOPT_WIDTH, RTGETOPT_REQ_UINT8 },
265
266 /* input selection */
267 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
268 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
269 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
270 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
271 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
272 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
273 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
274
275 /* rewriter selection */
276 { "--treat-as", SCMOPT_TREAT_AS, RTGETOPT_REQ_STRING },
277 { "--add-action", SCMOPT_ADD_ACTION, RTGETOPT_REQ_STRING },
278 { "--del-action", SCMOPT_DEL_ACTION, RTGETOPT_REQ_STRING },
279
280 /* Additional help */
281 { "--help-config", SCMOPT_HELP_CONFIG, RTGETOPT_REQ_NOTHING },
282 { "--help-actions", SCMOPT_HELP_ACTIONS, RTGETOPT_REQ_NOTHING },
283};
284
285/** Consider files matching the following patterns (base names only). */
286static const char *g_pszFileFilter = NULL;
287
288/* The rewriter configuration. */
289#define SCM_REWRITER_CFG(a_Global, a_szName, fnRewriter) static const SCMREWRITERCFG a_Global = { &fnRewriter, a_szName }
290SCM_REWRITER_CFG(g_StripTrailingBlanks, "strip-trailing-blanks", rewrite_StripTrailingBlanks);
291SCM_REWRITER_CFG(g_ExpandTabs, "expand-tabs", rewrite_ExpandTabs);
292SCM_REWRITER_CFG(g_ForceNativeEol, "force-native-eol", rewrite_ForceNativeEol);
293SCM_REWRITER_CFG(g_ForceLF, "force-lf", rewrite_ForceLF);
294SCM_REWRITER_CFG(g_ForceCRLF, "force-crlf", rewrite_ForceCRLF);
295SCM_REWRITER_CFG(g_AdjustTrailingLines, "adjust-trailing-lines", rewrite_AdjustTrailingLines);
296SCM_REWRITER_CFG(g_SvnNoExecutable, "svn-no-executable", rewrite_SvnNoExecutable);
297SCM_REWRITER_CFG(g_SvnNoKeywords, "svn-no-keywords", rewrite_SvnNoKeywords);
298SCM_REWRITER_CFG(g_SvnNoEolStyle, "svn-no-eol-style", rewrite_SvnNoEolStyle);
299SCM_REWRITER_CFG(g_SvnBinary, "svn-binary", rewrite_SvnBinary);
300SCM_REWRITER_CFG(g_SvnKeywords, "svn-keywords", rewrite_SvnKeywords);
301SCM_REWRITER_CFG(g_SvnSyncProcess, "svn-sync-process", rewrite_SvnSyncProcess);
302SCM_REWRITER_CFG(g_Copyright_CstyleComment, "copyright-c-style", rewrite_Copyright_CstyleComment);
303SCM_REWRITER_CFG(g_Copyright_HashComment, "copyright-hash-style", rewrite_Copyright_HashComment);
304SCM_REWRITER_CFG(g_Copyright_PythonComment, "copyright-python-style", rewrite_Copyright_PythonComment);
305SCM_REWRITER_CFG(g_Copyright_RemComment, "copyright-rem-style", rewrite_Copyright_RemComment);
306SCM_REWRITER_CFG(g_Copyright_SemicolonComment, "copyright-semicolon-style", rewrite_Copyright_SemicolonComment);
307SCM_REWRITER_CFG(g_Copyright_SqlComment, "copyright-sql-style", rewrite_Copyright_SqlComment);
308SCM_REWRITER_CFG(g_Copyright_TickComment, "copyright-tick-style", rewrite_Copyright_TickComment);
309SCM_REWRITER_CFG(g_Makefile_kup, "makefile-kup", rewrite_Makefile_kup);
310SCM_REWRITER_CFG(g_Makefile_kmk, "makefile-kmk", rewrite_Makefile_kmk);
311SCM_REWRITER_CFG(g_FixFlowerBoxMarkers, "fix-flower-boxes", rewrite_FixFlowerBoxMarkers);
312SCM_REWRITER_CFG(g_FixHeaderGuards, "fix-header-guard", rewrite_FixHeaderGuards);
313SCM_REWRITER_CFG(g_Fix_C_and_CPP_Todos, "fix-c-todos", rewrite_Fix_C_and_CPP_Todos);
314SCM_REWRITER_CFG(g_Fix_Err_H, "fix-err-h", rewrite_Fix_Err_H);
315SCM_REWRITER_CFG(g_C_and_CPP, "c-and-cpp", rewrite_C_and_CPP);
316
317/** The rewriter actions. */
318static PCSCMREWRITERCFG const g_papRewriterActions[] =
319{
320 &g_StripTrailingBlanks,
321 &g_ExpandTabs,
322 &g_ForceNativeEol,
323 &g_ForceLF,
324 &g_ForceCRLF,
325 &g_AdjustTrailingLines,
326 &g_SvnNoExecutable,
327 &g_SvnNoKeywords,
328 &g_SvnNoEolStyle,
329 &g_SvnBinary,
330 &g_SvnKeywords,
331 &g_SvnSyncProcess,
332 &g_Copyright_CstyleComment,
333 &g_Copyright_HashComment,
334 &g_Copyright_PythonComment,
335 &g_Copyright_RemComment,
336 &g_Copyright_SemicolonComment,
337 &g_Copyright_SqlComment,
338 &g_Copyright_TickComment,
339 &g_Makefile_kup,
340 &g_Makefile_kmk,
341 &g_FixFlowerBoxMarkers,
342 &g_FixHeaderGuards,
343 &g_Fix_C_and_CPP_Todos,
344 &g_Fix_Err_H,
345 &g_C_and_CPP,
346};
347
348
349static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kup[] =
350{
351 &g_SvnNoExecutable,
352 &g_SvnSyncProcess,
353 &g_Makefile_kup
354};
355
356static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kmk[] =
357{
358 &g_ForceNativeEol,
359 &g_StripTrailingBlanks,
360 &g_AdjustTrailingLines,
361 &g_SvnNoExecutable,
362 &g_SvnKeywords,
363 &g_SvnSyncProcess,
364 &g_Copyright_HashComment,
365 &g_Makefile_kmk
366};
367
368static PCSCMREWRITERCFG const g_apRewritersFor_OtherMakefiles[] =
369{
370 &g_ForceNativeEol,
371 &g_StripTrailingBlanks,
372 &g_AdjustTrailingLines,
373 &g_SvnNoExecutable,
374 &g_SvnKeywords,
375 &g_SvnSyncProcess,
376 &g_Copyright_HashComment,
377};
378
379static PCSCMREWRITERCFG const g_apRewritersFor_C_and_CPP[] =
380{
381 &g_ForceNativeEol,
382 &g_ExpandTabs,
383 &g_StripTrailingBlanks,
384 &g_AdjustTrailingLines,
385 &g_SvnNoExecutable,
386 &g_SvnKeywords,
387 &g_SvnSyncProcess,
388 &g_Copyright_CstyleComment,
389 &g_FixFlowerBoxMarkers,
390 &g_Fix_C_and_CPP_Todos,
391 &g_Fix_Err_H,
392 &g_C_and_CPP
393};
394
395static PCSCMREWRITERCFG const g_apRewritersFor_H_and_HPP[] =
396{
397 &g_ForceNativeEol,
398 &g_ExpandTabs,
399 &g_StripTrailingBlanks,
400 &g_AdjustTrailingLines,
401 &g_SvnNoExecutable,
402 &g_SvnKeywords,
403 &g_SvnSyncProcess,
404 &g_Copyright_CstyleComment,
405 /// @todo &g_FixFlowerBoxMarkers,
406 &g_FixHeaderGuards,
407 &g_C_and_CPP
408};
409
410static PCSCMREWRITERCFG const g_apRewritersFor_RC[] =
411{
412 &g_ForceNativeEol,
413 &g_ExpandTabs,
414 &g_StripTrailingBlanks,
415 &g_AdjustTrailingLines,
416 &g_SvnNoExecutable,
417 &g_SvnKeywords,
418 &g_SvnSyncProcess,
419 &g_Copyright_CstyleComment,
420};
421
422static PCSCMREWRITERCFG const g_apRewritersFor_DTrace[] =
423{
424 &g_ForceNativeEol,
425 &g_ExpandTabs,
426 &g_StripTrailingBlanks,
427 &g_AdjustTrailingLines,
428 &g_SvnKeywords,
429 &g_SvnSyncProcess,
430 &g_Copyright_CstyleComment,
431};
432
433static PCSCMREWRITERCFG const g_apRewritersFor_DSL[] =
434{
435 &g_ForceNativeEol,
436 &g_ExpandTabs,
437 &g_StripTrailingBlanks,
438 &g_AdjustTrailingLines,
439 &g_SvnNoExecutable,
440 &g_SvnKeywords,
441 &g_SvnSyncProcess,
442 &g_Copyright_CstyleComment,
443};
444
445static PCSCMREWRITERCFG const g_apRewritersFor_ASM[] =
446{
447 &g_ForceNativeEol,
448 &g_ExpandTabs,
449 &g_StripTrailingBlanks,
450 &g_AdjustTrailingLines,
451 &g_SvnNoExecutable,
452 &g_SvnKeywords,
453 &g_SvnSyncProcess,
454 &g_Copyright_SemicolonComment,
455};
456
457static PCSCMREWRITERCFG const g_apRewritersFor_DEF[] =
458{
459 &g_ForceNativeEol,
460 &g_ExpandTabs,
461 &g_StripTrailingBlanks,
462 &g_AdjustTrailingLines,
463 &g_SvnNoExecutable,
464 &g_SvnKeywords,
465 &g_SvnSyncProcess,
466 &g_Copyright_SemicolonComment,
467};
468
469static PCSCMREWRITERCFG const g_apRewritersFor_ShellScripts[] =
470{
471 &g_ForceLF,
472 &g_ExpandTabs,
473 &g_StripTrailingBlanks,
474 &g_SvnSyncProcess,
475 &g_Copyright_HashComment,
476};
477
478static PCSCMREWRITERCFG const g_apRewritersFor_BatchFiles[] =
479{
480 &g_ForceCRLF,
481 &g_ExpandTabs,
482 &g_StripTrailingBlanks,
483 &g_SvnSyncProcess,
484 &g_Copyright_RemComment,
485};
486
487static PCSCMREWRITERCFG const g_apRewritersFor_BasicScripts[] =
488{
489 &g_ForceCRLF,
490 &g_ExpandTabs,
491 &g_StripTrailingBlanks,
492 &g_SvnSyncProcess,
493 &g_Copyright_TickComment,
494};
495
496static PCSCMREWRITERCFG const g_apRewritersFor_SedScripts[] =
497{
498 &g_ForceLF,
499 &g_ExpandTabs,
500 &g_StripTrailingBlanks,
501 &g_SvnSyncProcess,
502 &g_Copyright_HashComment,
503};
504
505static PCSCMREWRITERCFG const g_apRewritersFor_Python[] =
506{
507 /** @todo &g_ForceLFIfExecutable */
508 &g_ExpandTabs,
509 &g_StripTrailingBlanks,
510 &g_AdjustTrailingLines,
511 &g_SvnKeywords,
512 &g_SvnSyncProcess,
513 &g_Copyright_PythonComment,
514};
515
516static PCSCMREWRITERCFG const g_apRewritersFor_Perl[] =
517{
518 /** @todo &g_ForceLFIfExecutable */
519 &g_ExpandTabs,
520 &g_StripTrailingBlanks,
521 &g_AdjustTrailingLines,
522 &g_SvnKeywords,
523 &g_SvnSyncProcess,
524 &g_Copyright_HashComment,
525};
526
527static PCSCMREWRITERCFG const g_apRewritersFor_DriverInfFiles[] =
528{
529 &g_ForceNativeEol,
530 &g_ExpandTabs,
531 &g_StripTrailingBlanks,
532 &g_AdjustTrailingLines,
533 &g_SvnKeywords,
534 &g_SvnNoExecutable,
535 &g_SvnSyncProcess,
536 &g_Copyright_SemicolonComment,
537};
538
539static PCSCMREWRITERCFG const g_apRewritersFor_NsisFiles[] =
540{
541 &g_ForceNativeEol,
542 &g_ExpandTabs,
543 &g_StripTrailingBlanks,
544 &g_AdjustTrailingLines,
545 &g_SvnKeywords,
546 &g_SvnNoExecutable,
547 &g_SvnSyncProcess,
548 &g_Copyright_SemicolonComment,
549};
550
551static PCSCMREWRITERCFG const g_apRewritersFor_Java[] =
552{
553 &g_ForceNativeEol,
554 &g_ExpandTabs,
555 &g_StripTrailingBlanks,
556 &g_AdjustTrailingLines,
557 &g_SvnNoExecutable,
558 &g_SvnKeywords,
559 &g_SvnSyncProcess,
560 &g_Copyright_CstyleComment,
561 &g_FixFlowerBoxMarkers,
562 &g_Fix_C_and_CPP_Todos,
563};
564
565static PCSCMREWRITERCFG const g_apRewritersFor_ScmSettings[] =
566{
567 &g_ForceNativeEol,
568 &g_ExpandTabs,
569 &g_StripTrailingBlanks,
570 &g_AdjustTrailingLines,
571 &g_SvnNoExecutable,
572 &g_SvnKeywords,
573 &g_SvnSyncProcess,
574 &g_Copyright_HashComment,
575};
576
577static PCSCMREWRITERCFG const g_apRewritersFor_Images[] =
578{
579 &g_SvnNoExecutable,
580 &g_SvnBinary,
581 &g_SvnSyncProcess,
582};
583
584static PCSCMREWRITERCFG const g_apRewritersFor_Xslt[] =
585{
586 &g_ForceNativeEol,
587 &g_ExpandTabs,
588 &g_StripTrailingBlanks,
589 &g_AdjustTrailingLines,
590 &g_SvnNoExecutable,
591 &g_SvnKeywords,
592 &g_SvnSyncProcess,
593 /** @todo copyright is in an XML comment. */
594};
595
596static PCSCMREWRITERCFG const g_apRewritersFor_Xml[] =
597{
598 &g_ForceNativeEol,
599 &g_ExpandTabs,
600 &g_StripTrailingBlanks,
601 &g_AdjustTrailingLines,
602 &g_SvnNoExecutable,
603 &g_SvnKeywords,
604 &g_SvnSyncProcess,
605 /** @todo copyright is in an XML comment. */
606};
607
608static PCSCMREWRITERCFG const g_apRewritersFor_Wix[] =
609{
610 &g_ForceNativeEol,
611 &g_ExpandTabs,
612 &g_StripTrailingBlanks,
613 &g_AdjustTrailingLines,
614 &g_SvnNoExecutable,
615 &g_SvnKeywords,
616 &g_SvnSyncProcess,
617 /** @todo copyright is in an XML comment. */
618};
619
620static PCSCMREWRITERCFG const g_apRewritersFor_QtProject[] =
621{
622 &g_ForceNativeEol,
623 &g_StripTrailingBlanks,
624 &g_AdjustTrailingLines,
625 &g_SvnNoExecutable,
626 &g_SvnKeywords,
627 &g_SvnSyncProcess,
628 &g_Copyright_HashComment,
629};
630
631static PCSCMREWRITERCFG const g_apRewritersFor_QtResourceFiles[] =
632{
633 &g_ForceNativeEol,
634 &g_SvnNoExecutable,
635 &g_SvnKeywords,
636 &g_SvnSyncProcess,
637 /** @todo figure out copyright for Qt resource XML files. */
638};
639
640static PCSCMREWRITERCFG const g_apRewritersFor_QtTranslations[] =
641{
642 &g_ForceNativeEol,
643 &g_SvnNoExecutable,
644};
645
646static PCSCMREWRITERCFG const g_apRewritersFor_QtUiFiles[] =
647{
648 &g_ForceNativeEol,
649 &g_SvnNoExecutable,
650 &g_SvnKeywords,
651 &g_SvnSyncProcess,
652 /** @todo copyright is in an XML 'comment' element. */
653};
654
655static PCSCMREWRITERCFG const g_apRewritersFor_SifFiles[] =
656{
657 &g_ForceCRLF,
658 &g_ExpandTabs,
659 &g_StripTrailingBlanks,
660 &g_AdjustTrailingLines,
661 &g_SvnKeywords,
662 &g_SvnNoExecutable,
663 &g_SvnSyncProcess,
664 &g_Copyright_SemicolonComment,
665};
666
667static PCSCMREWRITERCFG const g_apRewritersFor_SqlFiles[] =
668{
669 &g_ForceNativeEol,
670 &g_ExpandTabs,
671 &g_StripTrailingBlanks,
672 &g_AdjustTrailingLines,
673 &g_SvnKeywords,
674 &g_SvnNoExecutable,
675 &g_SvnSyncProcess,
676 &g_Copyright_SqlComment,
677};
678
679static PCSCMREWRITERCFG const g_apRewritersFor_GnuAsm[] =
680{
681 &g_ForceNativeEol,
682 &g_ExpandTabs,
683 &g_StripTrailingBlanks,
684 &g_AdjustTrailingLines,
685 &g_SvnKeywords,
686 &g_SvnNoExecutable,
687 &g_SvnSyncProcess,
688 &g_Copyright_CstyleComment,
689};
690
691static PCSCMREWRITERCFG const g_apRewritersFor_TextFiles[] =
692{
693 &g_ForceNativeEol,
694 &g_StripTrailingBlanks,
695 &g_SvnKeywords,
696 &g_SvnNoExecutable,
697 &g_SvnSyncProcess,
698 /** @todo check for plain copyright + license in text files. */
699};
700
701static PCSCMREWRITERCFG const g_apRewritersFor_PlainTextFiles[] =
702{
703 &g_ForceNativeEol,
704 &g_StripTrailingBlanks,
705 &g_SvnKeywords,
706 &g_SvnNoExecutable,
707 &g_SvnSyncProcess,
708};
709
710static PCSCMREWRITERCFG const g_apRewritersFor_BinaryFiles[] =
711{
712 &g_SvnBinary,
713 &g_SvnSyncProcess,
714};
715
716static PCSCMREWRITERCFG const g_apRewritersFor_FileLists[] = /* both makefile and shell script */
717{
718 &g_ForceLF,
719 &g_ExpandTabs,
720 &g_StripTrailingBlanks,
721 &g_AdjustTrailingLines,
722 &g_SvnSyncProcess,
723 &g_Copyright_HashComment,
724};
725
726
727/**
728 * Array of standard rewriter configurations.
729 */
730static SCMCFGENTRY const g_aConfigs[] =
731{
732#define SCM_CFG_ENTRY(a_szName, a_aRewriters, a_fBinary, a_szFilePatterns) \
733 { RT_ELEMENTS(a_aRewriters), &a_aRewriters[0], a_fBinary, a_szFilePatterns, a_szName }
734 SCM_CFG_ENTRY("kup", g_apRewritersFor_Makefile_kup, false, "Makefile.kup" ),
735 SCM_CFG_ENTRY("kmk", g_apRewritersFor_Makefile_kmk, false, "*.kmk" ),
736 SCM_CFG_ENTRY("c", g_apRewritersFor_C_and_CPP, false, "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm" ),
737 SCM_CFG_ENTRY("h", g_apRewritersFor_H_and_HPP, false, "*.h|*.hpp" ),
738 SCM_CFG_ENTRY("rc", g_apRewritersFor_RC, false, "*.rc" ),
739 SCM_CFG_ENTRY("asm", g_apRewritersFor_ASM, false, "*.asm|*.mac|*.inc" ),
740 SCM_CFG_ENTRY("dtrace", g_apRewritersFor_DTrace, false, "*.d" ),
741 SCM_CFG_ENTRY("def", g_apRewritersFor_DEF, false, "*.def" ),
742 SCM_CFG_ENTRY("iasl", g_apRewritersFor_DSL, false, "*.dsl" ),
743 SCM_CFG_ENTRY("shell", g_apRewritersFor_ShellScripts, false, "*.sh|configure" ),
744 SCM_CFG_ENTRY("batch", g_apRewritersFor_BatchFiles, false, "*.bat|*.cmd|*.btm" ),
745 SCM_CFG_ENTRY("vbs", g_apRewritersFor_BasicScripts, false, "*.vbs|*.vb" ),
746 SCM_CFG_ENTRY("sed", g_apRewritersFor_SedScripts, false, "*.sed" ),
747 SCM_CFG_ENTRY("python", g_apRewritersFor_Python, false, "*.py" ),
748 SCM_CFG_ENTRY("perl", g_apRewritersFor_Perl, false, "*.pl|*.pm" ),
749 SCM_CFG_ENTRY("drvinf", g_apRewritersFor_DriverInfFiles, false, "*.inf" ),
750 SCM_CFG_ENTRY("nsis", g_apRewritersFor_NsisFiles, false, "*.nsh|*.nsi|*.nsis" ),
751 SCM_CFG_ENTRY("java", g_apRewritersFor_Java, false, "*.java" ),
752 SCM_CFG_ENTRY("scm", g_apRewritersFor_ScmSettings, false, "*.scm-settings" ),
753 SCM_CFG_ENTRY("image", g_apRewritersFor_Images, true, "*.png|*.bmp|*.jpg|*.pnm|*.ico|*.icns|*.tiff|*.tif|*.xcf|*.gif" ),
754 SCM_CFG_ENTRY("xslt", g_apRewritersFor_Xslt, false, "*.xsl" ),
755 SCM_CFG_ENTRY("xml", g_apRewritersFor_Xml, false, "*.xml" ),
756 SCM_CFG_ENTRY("wix", g_apRewritersFor_Wix, false, "*.wxi|*.wxs|*.wxl" ),
757 SCM_CFG_ENTRY("qt-pro", g_apRewritersFor_QtProject, false, "*.pro" ),
758 SCM_CFG_ENTRY("qt-rc", g_apRewritersFor_QtResourceFiles, false, "*.qrc" ),
759 SCM_CFG_ENTRY("qt-ts", g_apRewritersFor_QtTranslations, false, "*.ts" ),
760 SCM_CFG_ENTRY("qt-ui", g_apRewritersFor_QtUiFiles, false, "*.ui" ),
761 SCM_CFG_ENTRY("sif", g_apRewritersFor_SifFiles, false, "*.sif" ),
762 SCM_CFG_ENTRY("sql", g_apRewritersFor_SqlFiles, false, "*.pgsql|*.sql" ),
763 SCM_CFG_ENTRY("gas", g_apRewritersFor_GnuAsm, false, "*.S" ),
764 SCM_CFG_ENTRY("binary", g_apRewritersFor_BinaryFiles, true, "*.bin|*.pdf|*.zip|*.bz2|*.gz" ),
765 /* These should be be last: */
766 SCM_CFG_ENTRY("make", g_apRewritersFor_OtherMakefiles, false, "Makefile|makefile|GNUmakefile|SMakefile|Makefile.am|Makefile.in|*.cmake" ),
767 SCM_CFG_ENTRY("text", g_apRewritersFor_TextFiles, false, "*.txt|README*|readme*|ReadMe*|NOTE*|TODO*" ),
768 SCM_CFG_ENTRY("plaintext", g_apRewritersFor_PlainTextFiles, false, "LICENSE|ChangeLog|FAQ|AUTHORS|INSTALL|NEWS" ),
769 SCM_CFG_ENTRY("file-list", g_apRewritersFor_FileLists, false, "files_*" ),
770};
771
772
773
774/* -=-=-=-=-=- settings -=-=-=-=-=- */
775
776/**
777 * Delete the given config entry.
778 *
779 * @param pEntry The configuration entry to delete.
780 */
781static void scmCfgEntryDelete(PSCMCFGENTRY pEntry)
782{
783 RTMemFree((void *)pEntry->paRewriters);
784 pEntry->paRewriters = NULL;
785 RTMemFree(pEntry);
786}
787
788/**
789 * Create a new configuration entry.
790 *
791 * @returns The new entry. NULL if out of memory.
792 * @param pEntry The configuration entry to duplicate.
793 */
794static PSCMCFGENTRY scmCfgEntryNew(void)
795{
796 PSCMCFGENTRY pNew = (PSCMCFGENTRY)RTMemAlloc(sizeof(*pNew));
797 if (pNew)
798 {
799 pNew->pszName = "custom";
800 pNew->pszFilePattern = "custom";
801 pNew->cRewriters = 0;
802 pNew->paRewriters = NULL;
803 pNew->fBinary = false;
804 }
805 return pNew;
806}
807
808/**
809 * Duplicate the given config entry.
810 *
811 * @returns The duplicate. NULL if out of memory.
812 * @param pEntry The configuration entry to duplicate.
813 */
814static PSCMCFGENTRY scmCfgEntryDup(PCSCMCFGENTRY pEntry)
815{
816 if (pEntry)
817 {
818 PSCMCFGENTRY pDup = (PSCMCFGENTRY)RTMemDup(pEntry, sizeof(*pEntry));
819 if (pDup)
820 {
821 size_t cbSrcRewriters = sizeof(pEntry->paRewriters[0]) * pEntry->cRewriters;
822 size_t cbDstRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z(pEntry->cRewriters, 8);
823 pDup->paRewriters = (PCSCMREWRITERCFG const *)RTMemDupEx(pEntry->paRewriters, cbSrcRewriters,
824 cbDstRewriters - cbSrcRewriters);
825 if (pDup->paRewriters)
826 return pDup;
827
828 RTMemFree(pDup);
829 }
830 return NULL;
831 }
832 return scmCfgEntryNew();
833}
834
835/**
836 * Adds a rewriter action to the given config entry (--add-action).
837 *
838 * @returns VINF_SUCCESS.
839 * @param pEntry The configuration entry.
840 * @param pAction The rewriter action to add.
841 */
842static int scmCfgEntryAddAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction)
843{
844 PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters;
845 if (pEntry->cRewriters % 8 == 0)
846 {
847 size_t cbRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z((pEntry->cRewriters + 1), 8);
848 void *pvNew = RTMemRealloc(paRewriters, cbRewriters);
849 if (pvNew)
850 pEntry->paRewriters = paRewriters = (PCSCMREWRITERCFG *)pvNew;
851 else
852 return VERR_NO_MEMORY;
853 }
854
855 paRewriters[pEntry->cRewriters++] = pAction;
856 return VINF_SUCCESS;
857}
858
859/**
860 * Delets an rewriter action from the given config entry (--del-action).
861 *
862 * @param pEntry The configuration entry.
863 * @param pAction The rewriter action to remove.
864 */
865static void scmCfgEntryDelAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction)
866{
867 PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters;
868 size_t const cEntries = pEntry->cRewriters;
869 size_t iDst = 0;
870 for (size_t iSrc = 0; iSrc < cEntries; iSrc++)
871 {
872 PCSCMREWRITERCFG pCurAction = paRewriters[iSrc];
873 if (pCurAction != pAction)
874 paRewriters[iDst++] = pCurAction;
875 }
876 pEntry->cRewriters = iDst;
877}
878
879/**
880 * Init a settings structure with settings from @a pSrc.
881 *
882 * @returns IPRT status code
883 * @param pSettings The settings.
884 * @param pSrc The source settings.
885 */
886static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
887{
888 *pSettings = *pSrc;
889
890 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
891 if (RT_SUCCESS(rc))
892 {
893 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
894 if (RT_SUCCESS(rc))
895 {
896 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
897 if (RT_SUCCESS(rc))
898 {
899 rc = RTStrDupEx(&pSettings->pszGuardPrefix, pSrc->pszGuardPrefix);
900 if (RT_SUCCESS(rc))
901 {
902 if (pSrc->pszGuardRelativeToDir)
903 rc = RTStrDupEx(&pSettings->pszGuardRelativeToDir, pSrc->pszGuardRelativeToDir);
904 if (RT_SUCCESS(rc))
905 {
906
907 if (!pSrc->fFreeTreatAs)
908 return VINF_SUCCESS;
909
910 pSettings->pTreatAs = scmCfgEntryDup(pSrc->pTreatAs);
911 if (pSettings->pTreatAs)
912 return VINF_SUCCESS;
913
914 RTStrFree(pSettings->pszGuardRelativeToDir);
915 }
916 RTStrFree(pSettings->pszGuardPrefix);
917 }
918 }
919 RTStrFree(pSettings->pszFilterOutFiles);
920 }
921 RTStrFree(pSettings->pszFilterFiles);
922 }
923
924 pSettings->pszGuardRelativeToDir = NULL;
925 pSettings->pszGuardPrefix = NULL;
926 pSettings->pszFilterFiles = NULL;
927 pSettings->pszFilterOutFiles = NULL;
928 pSettings->pszFilterOutDirs = NULL;
929 pSettings->pTreatAs = NULL;
930 return rc;
931}
932
933/**
934 * Init a settings structure.
935 *
936 * @returns IPRT status code
937 * @param pSettings The settings.
938 */
939static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
940{
941 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
942}
943
944/**
945 * Deletes the settings, i.e. free any dynamically allocated content.
946 *
947 * @param pSettings The settings.
948 */
949static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
950{
951 if (pSettings)
952 {
953 Assert(pSettings->cchTab != UINT8_MAX);
954 pSettings->cchTab = UINT8_MAX;
955
956 RTStrFree(pSettings->pszGuardPrefix);
957 RTStrFree(pSettings->pszGuardRelativeToDir);
958 RTStrFree(pSettings->pszFilterFiles);
959 RTStrFree(pSettings->pszFilterOutFiles);
960 RTStrFree(pSettings->pszFilterOutDirs);
961 if (pSettings->fFreeTreatAs)
962 scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs);
963
964 pSettings->pszGuardPrefix = NULL;
965 pSettings->pszGuardRelativeToDir = NULL;
966 pSettings->pszFilterOutDirs = NULL;
967 pSettings->pszFilterOutFiles = NULL;
968 pSettings->pszFilterFiles = NULL;
969 pSettings->pTreatAs = NULL;
970 pSettings->fFreeTreatAs = false;
971 }
972}
973
974/**
975 * Processes a RTGetOpt result.
976 *
977 * @retval VINF_SUCCESS if handled.
978 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
979 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
980 *
981 * @param pSettings The settings to change.
982 * @param rc The RTGetOpt return value.
983 * @param pValueUnion The RTGetOpt value union.
984 * @param pchDir The absolute path to the directory relative
985 * components in pchLine should be relative to.
986 * @param cchDir The length of the @a pchDir string.
987 */
988static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion,
989 const char *pchDir, size_t cchDir)
990{
991 Assert(pchDir[cchDir - 1] == '/');
992
993 switch (rc)
994 {
995 case SCMOPT_CONVERT_EOL:
996 pSettings->fConvertEol = true;
997 return VINF_SUCCESS;
998 case SCMOPT_NO_CONVERT_EOL:
999 pSettings->fConvertEol = false;
1000 return VINF_SUCCESS;
1001
1002 case SCMOPT_CONVERT_TABS:
1003 pSettings->fConvertTabs = true;
1004 return VINF_SUCCESS;
1005 case SCMOPT_NO_CONVERT_TABS:
1006 pSettings->fConvertTabs = false;
1007 return VINF_SUCCESS;
1008
1009 case SCMOPT_FORCE_FINAL_EOL:
1010 pSettings->fForceFinalEol = true;
1011 return VINF_SUCCESS;
1012 case SCMOPT_NO_FORCE_FINAL_EOL:
1013 pSettings->fForceFinalEol = false;
1014 return VINF_SUCCESS;
1015
1016 case SCMOPT_FORCE_TRAILING_LINE:
1017 pSettings->fForceTrailingLine = true;
1018 return VINF_SUCCESS;
1019 case SCMOPT_NO_FORCE_TRAILING_LINE:
1020 pSettings->fForceTrailingLine = false;
1021 return VINF_SUCCESS;
1022
1023
1024 case SCMOPT_STRIP_TRAILING_BLANKS:
1025 pSettings->fStripTrailingBlanks = true;
1026 return VINF_SUCCESS;
1027 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
1028 pSettings->fStripTrailingBlanks = false;
1029 return VINF_SUCCESS;
1030
1031 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS:
1032 pSettings->cMinBlankLinesBeforeFlowerBoxMakers = pValueUnion->u8;
1033 return VINF_SUCCESS;
1034
1035
1036 case SCMOPT_STRIP_TRAILING_LINES:
1037 pSettings->fStripTrailingLines = true;
1038 return VINF_SUCCESS;
1039 case SCMOPT_NO_STRIP_TRAILING_LINES:
1040 pSettings->fStripTrailingLines = false;
1041 return VINF_SUCCESS;
1042
1043 case SCMOPT_FIX_FLOWER_BOX_MARKERS:
1044 pSettings->fFixFlowerBoxMarkers = true;
1045 return VINF_SUCCESS;
1046 case SCMOPT_NO_FIX_FLOWER_BOX_MARKERS:
1047 pSettings->fFixFlowerBoxMarkers = false;
1048 return VINF_SUCCESS;
1049
1050 case SCMOPT_FIX_HEADER_GUARDS:
1051 pSettings->fFixHeaderGuards = true;
1052 return VINF_SUCCESS;
1053 case SCMOPT_NO_FIX_HEADER_GUARDS:
1054 pSettings->fFixHeaderGuards = false;
1055 return VINF_SUCCESS;
1056
1057 case SCMOPT_PRAGMA_ONCE:
1058 pSettings->fPragmaOnce = true;
1059 return VINF_SUCCESS;
1060 case SCMOPT_NO_PRAGMA_ONCE:
1061 pSettings->fPragmaOnce = false;
1062 return VINF_SUCCESS;
1063
1064 case SCMOPT_ENDIF_GUARD_COMMENT:
1065 pSettings->fEndifGuardComment = true;
1066 return VINF_SUCCESS;
1067 case SCMOPT_NO_ENDIF_GUARD_COMMENT:
1068 pSettings->fEndifGuardComment = false;
1069 return VINF_SUCCESS;
1070
1071 case SCMOPT_GUARD_PREFIX:
1072 RTStrFree(pSettings->pszGuardPrefix);
1073 pSettings->pszGuardPrefix = NULL;
1074 return RTStrDupEx(&pSettings->pszGuardPrefix, pValueUnion->psz);
1075
1076 case SCMOPT_GUARD_RELATIVE_TO_DIR:
1077 RTStrFree(pSettings->pszGuardRelativeToDir);
1078 pSettings->pszGuardRelativeToDir = NULL;
1079 if (*pValueUnion->psz != '\0')
1080 {
1081 if (cchDir == 1 && *pchDir == '/')
1082 {
1083 pSettings->pszGuardRelativeToDir = RTPathAbsDup(pValueUnion->psz);
1084 if (pSettings->pszGuardRelativeToDir)
1085 return VINF_SUCCESS;
1086 }
1087 else
1088 {
1089 char *pszDir = RTStrDupN(pchDir, cchDir);
1090 if (pszDir)
1091 {
1092 pSettings->pszGuardRelativeToDir = RTPathAbsExDup(pszDir, pValueUnion->psz);
1093 RTStrFree(pszDir);
1094 if (pSettings->pszGuardRelativeToDir)
1095 return VINF_SUCCESS;
1096 }
1097 }
1098 RTMsgError("Failed to abspath --guard-relative-to-dir value '%s' - probably out of memory\n", pValueUnion->psz);
1099 return VERR_NO_STR_MEMORY;
1100 }
1101 return VINF_SUCCESS;
1102
1103 case SCMOPT_FIX_TODOS:
1104 pSettings->fFixTodos = true;
1105 return VINF_SUCCESS;
1106 case SCMOPT_NO_FIX_TODOS:
1107 pSettings->fFixTodos = false;
1108 return VINF_SUCCESS;
1109
1110 case SCMOPT_FIX_ERR_H:
1111 pSettings->fFixErrH = true;
1112 return VINF_SUCCESS;
1113 case SCMOPT_NO_FIX_ERR_H:
1114 pSettings->fFixErrH = false;
1115 return VINF_SUCCESS;
1116
1117 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
1118 pSettings->fUpdateCopyrightYear = true;
1119 return VINF_SUCCESS;
1120 case SCMOPT_NO_UPDATE_COPYRIGHT_YEAR:
1121 pSettings->fUpdateCopyrightYear = false;
1122 return VINF_SUCCESS;
1123
1124 case SCMOPT_EXTERNAL_COPYRIGHT:
1125 pSettings->fExternalCopyright = true;
1126 return VINF_SUCCESS;
1127 case SCMOPT_NO_EXTERNAL_COPYRIGHT:
1128 pSettings->fExternalCopyright = false;
1129 return VINF_SUCCESS;
1130
1131 case SCMOPT_NO_UPDATE_LICENSE:
1132 pSettings->enmUpdateLicense = kScmLicense_LeaveAlone;
1133 return VINF_SUCCESS;
1134 case SCMOPT_LICENSE_OSE_GPL:
1135 pSettings->enmUpdateLicense = kScmLicense_OseGpl;
1136 return VINF_SUCCESS;
1137 case SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL:
1138 pSettings->enmUpdateLicense = kScmLicense_OseDualGplCddl;
1139 return VINF_SUCCESS;
1140 case SCMOPT_LICENSE_OSE_CDDL:
1141 pSettings->enmUpdateLicense = kScmLicense_OseCddl;
1142 return VINF_SUCCESS;
1143 case SCMOPT_LICENSE_LGPL:
1144 pSettings->enmUpdateLicense = kScmLicense_Lgpl;
1145 return VINF_SUCCESS;
1146 case SCMOPT_LICENSE_MIT:
1147 pSettings->enmUpdateLicense = kScmLicense_Mit;
1148 return VINF_SUCCESS;
1149 case SCMOPT_LICENSE_BASED_ON_MIT:
1150 pSettings->enmUpdateLicense = kScmLicense_BasedOnMit;
1151 return VINF_SUCCESS;
1152
1153 case SCMOPT_LGPL_DISCLAIMER:
1154 pSettings->fLgplDisclaimer = true;
1155 return VINF_SUCCESS;
1156 case SCMOPT_NO_LGPL_DISCLAIMER:
1157 pSettings->fLgplDisclaimer = false;
1158 return VINF_SUCCESS;
1159
1160 case SCMOPT_ONLY_SVN_DIRS:
1161 pSettings->fOnlySvnDirs = true;
1162 return VINF_SUCCESS;
1163 case SCMOPT_NOT_ONLY_SVN_DIRS:
1164 pSettings->fOnlySvnDirs = false;
1165 return VINF_SUCCESS;
1166
1167 case SCMOPT_ONLY_SVN_FILES:
1168 pSettings->fOnlySvnFiles = true;
1169 return VINF_SUCCESS;
1170 case SCMOPT_NOT_ONLY_SVN_FILES:
1171 pSettings->fOnlySvnFiles = false;
1172 return VINF_SUCCESS;
1173
1174 case SCMOPT_SET_SVN_EOL:
1175 pSettings->fSetSvnEol = true;
1176 return VINF_SUCCESS;
1177 case SCMOPT_DONT_SET_SVN_EOL:
1178 pSettings->fSetSvnEol = false;
1179 return VINF_SUCCESS;
1180
1181 case SCMOPT_SET_SVN_EXECUTABLE:
1182 pSettings->fSetSvnExecutable = true;
1183 return VINF_SUCCESS;
1184 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
1185 pSettings->fSetSvnExecutable = false;
1186 return VINF_SUCCESS;
1187
1188 case SCMOPT_SET_SVN_KEYWORDS:
1189 pSettings->fSetSvnKeywords = true;
1190 return VINF_SUCCESS;
1191 case SCMOPT_DONT_SET_SVN_KEYWORDS:
1192 pSettings->fSetSvnKeywords = false;
1193 return VINF_SUCCESS;
1194
1195 case SCMOPT_SKIP_SVN_SYNC_PROCESS:
1196 pSettings->fSkipSvnSyncProcess = true;
1197 return VINF_SUCCESS;
1198 case SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS:
1199 pSettings->fSkipSvnSyncProcess = false;
1200 return VINF_SUCCESS;
1201
1202 case SCMOPT_TAB_SIZE:
1203 if ( pValueUnion->u8 < 1
1204 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
1205 {
1206 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
1207 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
1208 return VERR_OUT_OF_RANGE;
1209 }
1210 pSettings->cchTab = pValueUnion->u8;
1211 return VINF_SUCCESS;
1212
1213 case SCMOPT_WIDTH:
1214 if (pValueUnion->u8 < 20 || pValueUnion->u8 > 200)
1215 {
1216 RTMsgError("Invalid width size: %u - must be in {20..200} range\n", pValueUnion->u8);
1217 return VERR_OUT_OF_RANGE;
1218 }
1219 pSettings->cchWidth = pValueUnion->u8;
1220 return VINF_SUCCESS;
1221
1222 case SCMOPT_FILTER_OUT_DIRS:
1223 case SCMOPT_FILTER_FILES:
1224 case SCMOPT_FILTER_OUT_FILES:
1225 {
1226 char **ppsz = NULL;
1227 switch (rc)
1228 {
1229 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
1230 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
1231 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
1232 }
1233
1234 /*
1235 * An empty string zaps the current list.
1236 */
1237 if (!*pValueUnion->psz)
1238 return RTStrATruncate(ppsz, 0);
1239
1240 /*
1241 * Non-empty strings are appended to the pattern list.
1242 *
1243 * Strip leading and trailing pattern separators before attempting
1244 * to append it. If it's just separators, don't do anything.
1245 */
1246 const char *pszSrc = pValueUnion->psz;
1247 while (*pszSrc == '|')
1248 pszSrc++;
1249 size_t cchSrc = strlen(pszSrc);
1250 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
1251 cchSrc--;
1252 if (!cchSrc)
1253 return VINF_SUCCESS;
1254
1255 /* Append it pattern by pattern, turning settings-relative paths into absolute ones. */
1256 for (;;)
1257 {
1258 const char *pszEnd = (const char *)memchr(pszSrc, '|', cchSrc);
1259 size_t cchPattern = pszEnd ? pszEnd - pszSrc : cchSrc;
1260 int rc2;
1261 if (*pszSrc == '/')
1262 rc2 = RTStrAAppendExN(ppsz, 3,
1263 "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
1264 pchDir, cchDir - 1,
1265 pszSrc, cchPattern);
1266 else
1267 rc2 = RTStrAAppendExN(ppsz, 2,
1268 "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
1269 pszSrc, cchPattern);
1270 if (RT_FAILURE(rc2))
1271 return rc2;
1272
1273 /* next */
1274 cchSrc -= cchPattern;
1275 if (!cchSrc)
1276 return VINF_SUCCESS;
1277 cchSrc -= 1;
1278 pszSrc += cchPattern + 1;
1279 }
1280 /* not reached */
1281 }
1282
1283 case SCMOPT_TREAT_AS:
1284 if (pSettings->fFreeTreatAs)
1285 {
1286 scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs);
1287 pSettings->pTreatAs = NULL;
1288 pSettings->fFreeTreatAs = false;
1289 }
1290
1291 if (*pValueUnion->psz)
1292 {
1293 /* first check the names, then patterns (legacy). */
1294 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1295 if (strcmp(g_aConfigs[iCfg].pszName, pValueUnion->psz) == 0)
1296 {
1297 pSettings->pTreatAs = &g_aConfigs[iCfg];
1298 return VINF_SUCCESS;
1299 }
1300 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1301 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX,
1302 pValueUnion->psz, RTSTR_MAX, NULL))
1303 {
1304 pSettings->pTreatAs = &g_aConfigs[iCfg];
1305 return VINF_SUCCESS;
1306 }
1307 /* Special help for listing the possibilities? */
1308 if (strcmp(pValueUnion->psz, "help") == 0)
1309 {
1310 RTPrintf("Possible --treat-as values:\n");
1311 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1312 RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern);
1313 }
1314 return VERR_NOT_FOUND;
1315 }
1316
1317 pSettings->pTreatAs = NULL;
1318 return VINF_SUCCESS;
1319
1320 case SCMOPT_ADD_ACTION:
1321 for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++)
1322 if (strcmp(g_papRewriterActions[iAction]->pszName, pValueUnion->psz) == 0)
1323 {
1324 PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs;
1325 if (!pSettings->fFreeTreatAs)
1326 {
1327 pEntry = scmCfgEntryDup(pEntry);
1328 if (!pEntry)
1329 return VERR_NO_MEMORY;
1330 pSettings->pTreatAs = pEntry;
1331 pSettings->fFreeTreatAs = true;
1332 }
1333 return scmCfgEntryAddAction(pEntry, g_papRewriterActions[iAction]);
1334 }
1335 RTMsgError("Unknown --add-action value '%s'. Try --help-actions for a list.", pValueUnion->psz);
1336 return VERR_NOT_FOUND;
1337
1338 case SCMOPT_DEL_ACTION:
1339 {
1340 uint32_t cActions = 0;
1341 for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++)
1342 if (RTStrSimplePatternMatch(pValueUnion->psz, g_papRewriterActions[iAction]->pszName))
1343 {
1344 cActions++;
1345 PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs;
1346 if (!pSettings->fFreeTreatAs)
1347 {
1348 pEntry = scmCfgEntryDup(pEntry);
1349 if (!pEntry)
1350 return VERR_NO_MEMORY;
1351 pSettings->pTreatAs = pEntry;
1352 pSettings->fFreeTreatAs = true;
1353 }
1354 scmCfgEntryDelAction(pEntry, g_papRewriterActions[iAction]);
1355 if (!strchr(pValueUnion->psz, '*'))
1356 return VINF_SUCCESS;
1357 }
1358 if (cActions > 0)
1359 return VINF_SUCCESS;
1360 RTMsgError("Unknown --del-action value '%s'. Try --help-actions for a list.", pValueUnion->psz);
1361 return VERR_NOT_FOUND;
1362 }
1363
1364 default:
1365 return VERR_GETOPT_UNKNOWN_OPTION;
1366 }
1367}
1368
1369/**
1370 * Parses an option string.
1371 *
1372 * @returns IPRT status code.
1373 * @param pBase The base settings structure to apply the options
1374 * to.
1375 * @param pszOptions The options to parse.
1376 * @param pchDir The absolute path to the directory relative
1377 * components in pchLine should be relative to.
1378 * @param cchDir The length of the @a pchDir string.
1379 */
1380static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine, const char *pchDir, size_t cchDir)
1381{
1382 int cArgs;
1383 char **papszArgs;
1384 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
1385 if (RT_SUCCESS(rc))
1386 {
1387 RTGETOPTUNION ValueUnion;
1388 RTGETOPTSTATE GetOptState;
1389 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
1390 if (RT_SUCCESS(rc))
1391 {
1392 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1393 {
1394 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion, pchDir, cchDir);
1395 if (RT_FAILURE(rc))
1396 break;
1397 }
1398 }
1399 RTGetOptArgvFree(papszArgs);
1400 }
1401
1402 return rc;
1403}
1404
1405/**
1406 * Parses an unterminated option string.
1407 *
1408 * @returns IPRT status code.
1409 * @param pBase The base settings structure to apply the options
1410 * to.
1411 * @param pchLine The line.
1412 * @param cchLine The line length.
1413 * @param pchDir The absolute path to the directory relative
1414 * components in pchLine should be relative to.
1415 * @param cchDir The length of the @a pchDir string.
1416 */
1417static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine,
1418 const char *pchDir, size_t cchDir)
1419{
1420 char *pszLine = RTStrDupN(pchLine, cchLine);
1421 if (!pszLine)
1422 return VERR_NO_MEMORY;
1423 int rc = scmSettingsBaseParseString(pBase, pszLine, pchDir, cchDir);
1424 RTStrFree(pszLine);
1425 return rc;
1426}
1427
1428/**
1429 * Verifies the options string.
1430 *
1431 * @returns IPRT status code.
1432 * @param pszOptions The options to verify .
1433 */
1434static int scmSettingsBaseVerifyString(const char *pszOptions)
1435{
1436 SCMSETTINGSBASE Base;
1437 int rc = scmSettingsBaseInit(&Base);
1438 if (RT_SUCCESS(rc))
1439 {
1440 rc = scmSettingsBaseParseString(&Base, pszOptions, "/", 1);
1441 scmSettingsBaseDelete(&Base);
1442 }
1443 return rc;
1444}
1445
1446/**
1447 * Loads settings found in editor and SCM settings directives within the
1448 * document (@a pStream).
1449 *
1450 * @returns IPRT status code.
1451 * @param pBase The settings base to load settings into.
1452 * @param pStream The stream to scan for settings directives.
1453 */
1454static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
1455{
1456 /** @todo Editor and SCM settings directives in documents. */
1457 RT_NOREF2(pBase, pStream);
1458 return VINF_SUCCESS;
1459}
1460
1461/**
1462 * Creates a new settings file struct, cloning @a pSettings.
1463 *
1464 * @returns IPRT status code.
1465 * @param ppSettings Where to return the new struct.
1466 * @param pSettingsBase The settings to inherit from.
1467 */
1468static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
1469{
1470 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
1471 if (!pSettings)
1472 return VERR_NO_MEMORY;
1473 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
1474 if (RT_SUCCESS(rc))
1475 {
1476 pSettings->pDown = NULL;
1477 pSettings->pUp = NULL;
1478 pSettings->paPairs = NULL;
1479 pSettings->cPairs = 0;
1480 *ppSettings = pSettings;
1481 return VINF_SUCCESS;
1482 }
1483 RTMemFree(pSettings);
1484 return rc;
1485}
1486
1487/**
1488 * Destroys a settings structure.
1489 *
1490 * @param pSettings The settings structure to destroy. NULL is OK.
1491 */
1492static void scmSettingsDestroy(PSCMSETTINGS pSettings)
1493{
1494 if (pSettings)
1495 {
1496 scmSettingsBaseDelete(&pSettings->Base);
1497 for (size_t i = 0; i < pSettings->cPairs; i++)
1498 {
1499 RTStrFree(pSettings->paPairs[i].pszPattern);
1500 RTStrFree(pSettings->paPairs[i].pszOptions);
1501 RTStrFree(pSettings->paPairs[i].pszRelativeTo);
1502 pSettings->paPairs[i].pszPattern = NULL;
1503 pSettings->paPairs[i].pszOptions = NULL;
1504 pSettings->paPairs[i].pszRelativeTo = NULL;
1505 }
1506 RTMemFree(pSettings->paPairs);
1507 pSettings->paPairs = NULL;
1508 RTMemFree(pSettings);
1509 }
1510}
1511
1512/**
1513 * Adds a pattern/options pair to the settings structure.
1514 *
1515 * @returns IPRT status code.
1516 * @param pSettings The settings.
1517 * @param pchLine The line containing the unparsed pair.
1518 * @param cchLine The length of the line.
1519 * @param offColon The offset of the colon into the line.
1520 * @param pchDir The absolute path to the directory relative
1521 * components in pchLine should be relative to.
1522 * @param cchDir The length of the @a pchDir string.
1523 */
1524static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine, size_t offColon,
1525 const char *pchDir, size_t cchDir)
1526{
1527 Assert(pchLine[offColon] == ':' && offColon < cchLine);
1528 Assert(pchDir[cchDir - 1] == '/');
1529
1530 /*
1531 * Split the string.
1532 */
1533 size_t cchPattern = offColon;
1534 size_t cchOptions = cchLine - cchPattern - 1;
1535
1536 /* strip spaces everywhere */
1537 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
1538 cchPattern--;
1539 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
1540 cchPattern--, pchLine++;
1541
1542 const char *pchOptions = &pchLine[offColon + 1];
1543 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
1544 cchOptions--;
1545 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
1546 cchOptions--, pchOptions++;
1547
1548 /* Quietly ignore empty patterns and empty options. */
1549 if (!cchOptions || !cchPattern)
1550 return VINF_SUCCESS;
1551
1552 /*
1553 * Prepair the pair and verify the option string.
1554 */
1555 uint32_t iPair = pSettings->cPairs;
1556 if ((iPair % 32) == 0)
1557 {
1558 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
1559 if (!pvNew)
1560 return VERR_NO_MEMORY;
1561 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
1562 }
1563
1564 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
1565 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
1566 pSettings->paPairs[iPair].pszRelativeTo = RTStrDupN(pchDir, cchDir);
1567 int rc;
1568 if ( pSettings->paPairs[iPair].pszPattern
1569 && pSettings->paPairs[iPair].pszOptions
1570 && pSettings->paPairs[iPair].pszRelativeTo)
1571 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
1572 else
1573 rc = VERR_NO_MEMORY;
1574
1575 /*
1576 * If it checked out fine, expand any relative paths in the pattern.
1577 */
1578 if (RT_SUCCESS(rc))
1579 {
1580 size_t cPattern = 1;
1581 size_t cRelativePaths = 0;
1582 const char *pszSrc = pSettings->paPairs[iPair].pszPattern;
1583 for (;;)
1584 {
1585 if (*pszSrc == '/')
1586 cRelativePaths++;
1587 pszSrc = strchr(pszSrc, '|');
1588 if (!pszSrc)
1589 break;
1590 pszSrc++;
1591 cPattern++;
1592 }
1593 pSettings->paPairs[iPair].fMultiPattern = cPattern > 1;
1594 if (cRelativePaths > 0)
1595 {
1596 char *pszNewPattern = RTStrAlloc(cchPattern + cRelativePaths * (cchDir - 1) + 1);
1597 if (pszNewPattern)
1598 {
1599 char *pszDst = pszNewPattern;
1600 pszSrc = pSettings->paPairs[iPair].pszPattern;
1601 for (;;)
1602 {
1603 if (*pszSrc == '/')
1604 {
1605 memcpy(pszDst, pchDir, cchDir);
1606 pszDst += cchDir;
1607 pszSrc += 1;
1608 }
1609
1610 /* Look for the next relative path. */
1611 const char *pszSrcNext = strchr(pszSrc, '|');
1612 while (pszSrcNext && pszSrcNext[1] != '/')
1613 pszSrcNext = strchr(pszSrcNext, '|');
1614 if (!pszSrcNext)
1615 break;
1616
1617 /* Copy stuff between current and the next path. */
1618 pszSrcNext++;
1619 memcpy(pszDst, pszSrc, pszSrcNext - pszSrc);
1620 pszDst += pszSrcNext - pszSrc;
1621 pszSrc = pszSrcNext;
1622 }
1623
1624 /* Copy the final portion and replace the pattern. */
1625 strcpy(pszDst, pszSrc);
1626
1627 RTStrFree(pSettings->paPairs[iPair].pszPattern);
1628 pSettings->paPairs[iPair].pszPattern = pszNewPattern;
1629 }
1630 else
1631 rc = VERR_NO_MEMORY;
1632 }
1633 }
1634 if (RT_SUCCESS(rc))
1635 /*
1636 * Commit the pair.
1637 */
1638 pSettings->cPairs = iPair + 1;
1639 else
1640 {
1641 RTStrFree(pSettings->paPairs[iPair].pszPattern);
1642 RTStrFree(pSettings->paPairs[iPair].pszOptions);
1643 RTStrFree(pSettings->paPairs[iPair].pszRelativeTo);
1644 }
1645 return rc;
1646}
1647
1648/**
1649 * Loads in the settings from @a pszFilename.
1650 *
1651 * @returns IPRT status code.
1652 * @param pSettings Where to load the settings file.
1653 * @param pszFilename The file to load.
1654 */
1655static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
1656{
1657 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
1658
1659 /* Turn filename into an absolute path and drop the filename. */
1660 char szAbsPath[RTPATH_MAX];
1661 int rc = RTPathAbs(pszFilename, szAbsPath, sizeof(szAbsPath));
1662 if (RT_FAILURE(rc))
1663 {
1664 RTMsgError("%s: RTPathAbs -> %Rrc\n", pszFilename, rc);
1665 return rc;
1666 }
1667 RTPathChangeToUnixSlashes(szAbsPath, true);
1668 size_t cchDir = RTPathFilename(szAbsPath) - &szAbsPath[0];
1669
1670 /* Try open it.*/
1671 SCMSTREAM Stream;
1672 rc = ScmStreamInitForReading(&Stream, pszFilename);
1673 if (RT_SUCCESS(rc))
1674 {
1675 SCMEOL enmEol;
1676 const char *pchLine;
1677 size_t cchLine;
1678 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1679 {
1680 /* Ignore leading spaces. */
1681 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
1682 pchLine++, cchLine--;
1683
1684 /* Ignore empty lines and comment lines. */
1685 if (cchLine < 1 || *pchLine == '#')
1686 continue;
1687
1688 /* Deal with escaped newlines. */
1689 size_t iFirstLine = ~(size_t)0;
1690 char *pszFreeLine = NULL;
1691 if ( pchLine[cchLine - 1] == '\\'
1692 && ( cchLine < 2
1693 || pchLine[cchLine - 2] != '\\') )
1694 {
1695 iFirstLine = ScmStreamTellLine(&Stream);
1696
1697 cchLine--;
1698 while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1]))
1699 cchLine--;
1700
1701 size_t cchTotal = cchLine;
1702 pszFreeLine = RTStrDupN(pchLine, cchLine);
1703 if (pszFreeLine)
1704 {
1705 /* Append following lines. */
1706 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1707 {
1708 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
1709 pchLine++, cchLine--;
1710
1711 bool const fDone = cchLine == 0
1712 || pchLine[cchLine - 1] != '\\'
1713 || (cchLine >= 2 && pchLine[cchLine - 2] == '\\');
1714 if (!fDone)
1715 {
1716 cchLine--;
1717 while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1]))
1718 cchLine--;
1719 }
1720
1721 rc = RTStrRealloc(&pszFreeLine, cchTotal + 1 + cchLine + 1);
1722 if (RT_FAILURE(rc))
1723 break;
1724 pszFreeLine[cchTotal++] = ' ';
1725 memcpy(&pszFreeLine[cchTotal], pchLine, cchLine);
1726 cchTotal += cchLine;
1727 pszFreeLine[cchTotal] = '\0';
1728
1729 if (fDone)
1730 break;
1731 }
1732 }
1733 else
1734 rc = VERR_NO_STR_MEMORY;
1735
1736 if (RT_FAILURE(rc))
1737 {
1738 RTStrFree(pszFreeLine);
1739 rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: Ran out of memory deal with escaped newlines", pszFilename);
1740 break;
1741 }
1742
1743 pchLine = pszFreeLine;
1744 cchLine = cchTotal;
1745 }
1746
1747 /* What kind of line is it? */
1748 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
1749 if (pchColon)
1750 rc = scmSettingsAddPair(pSettings, pchLine, cchLine, pchColon - pchLine, szAbsPath, cchDir);
1751 else
1752 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine, szAbsPath, cchDir);
1753 if (pszFreeLine)
1754 RTStrFree(pszFreeLine);
1755 if (RT_FAILURE(rc))
1756 {
1757 RTMsgError("%s:%d: %Rrc\n",
1758 pszFilename, iFirstLine == ~(size_t)0 ? ScmStreamTellLine(&Stream) : iFirstLine, rc);
1759 break;
1760 }
1761 }
1762
1763 if (RT_SUCCESS(rc))
1764 {
1765 rc = ScmStreamGetStatus(&Stream);
1766 if (RT_FAILURE(rc))
1767 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
1768 }
1769 ScmStreamDelete(&Stream);
1770 }
1771 else
1772 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
1773 return rc;
1774}
1775
1776#if 0 /* unused */
1777/**
1778 * Parse the specified settings file creating a new settings struct from it.
1779 *
1780 * @returns IPRT status code
1781 * @param ppSettings Where to return the new settings.
1782 * @param pszFilename The file to parse.
1783 * @param pSettingsBase The base settings we inherit from.
1784 */
1785static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
1786{
1787 PSCMSETTINGS pSettings;
1788 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
1789 if (RT_SUCCESS(rc))
1790 {
1791 rc = scmSettingsLoadFile(pSettings, pszFilename, RTPathFilename(pszFilename) - pszFilename);
1792 if (RT_SUCCESS(rc))
1793 {
1794 *ppSettings = pSettings;
1795 return VINF_SUCCESS;
1796 }
1797
1798 scmSettingsDestroy(pSettings);
1799 }
1800 *ppSettings = NULL;
1801 return rc;
1802}
1803#endif
1804
1805
1806/**
1807 * Create an initial settings structure when starting processing a new file or
1808 * directory.
1809 *
1810 * This will look for .scm-settings files from the root and down to the
1811 * specified directory, combining them into the returned settings structure.
1812 *
1813 * @returns IPRT status code.
1814 * @param ppSettings Where to return the pointer to the top stack
1815 * object.
1816 * @param pBaseSettings The base settings we inherit from (globals
1817 * typically).
1818 * @param pszPath The absolute path to the new directory or file.
1819 */
1820static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
1821{
1822 *ppSettings = NULL; /* try shut up gcc. */
1823
1824 /*
1825 * We'll be working with a stack copy of the path.
1826 */
1827 char szFile[RTPATH_MAX];
1828 size_t cchDir = strlen(pszPath);
1829 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
1830 return VERR_FILENAME_TOO_LONG;
1831
1832 /*
1833 * Create the bottom-most settings.
1834 */
1835 PSCMSETTINGS pSettings;
1836 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
1837 if (RT_FAILURE(rc))
1838 return rc;
1839
1840 /*
1841 * Enumerate the path components from the root and down. Load any setting
1842 * files we find.
1843 */
1844 size_t cComponents = RTPathCountComponents(pszPath);
1845 for (size_t i = 1; i <= cComponents; i++)
1846 {
1847 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
1848 if (RT_SUCCESS(rc))
1849 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
1850 if (RT_FAILURE(rc))
1851 break;
1852 RTPathChangeToUnixSlashes(szFile, true);
1853
1854 if (RTFileExists(szFile))
1855 {
1856 rc = scmSettingsLoadFile(pSettings, szFile);
1857 if (RT_FAILURE(rc))
1858 break;
1859 }
1860 }
1861
1862 if (RT_SUCCESS(rc))
1863 *ppSettings = pSettings;
1864 else
1865 scmSettingsDestroy(pSettings);
1866 return rc;
1867}
1868
1869/**
1870 * Pushes a new settings set onto the stack.
1871 *
1872 * @param ppSettingsStack The pointer to the pointer to the top stack
1873 * element. This will be used as input and output.
1874 * @param pSettings The settings to push onto the stack.
1875 */
1876static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
1877{
1878 PSCMSETTINGS pOld = *ppSettingsStack;
1879 pSettings->pDown = pOld;
1880 pSettings->pUp = NULL;
1881 if (pOld)
1882 pOld->pUp = pSettings;
1883 *ppSettingsStack = pSettings;
1884}
1885
1886/**
1887 * Pushes the settings of the specified directory onto the stack.
1888 *
1889 * We will load any .scm-settings in the directory. A stack entry is added even
1890 * if no settings file was found.
1891 *
1892 * @returns IPRT status code.
1893 * @param ppSettingsStack The pointer to the pointer to the top stack
1894 * element. This will be used as input and output.
1895 * @param pszDir The directory to do this for.
1896 */
1897static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
1898{
1899 char szFile[RTPATH_MAX];
1900 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
1901 if (RT_SUCCESS(rc))
1902 {
1903 RTPathChangeToUnixSlashes(szFile, true);
1904
1905 PSCMSETTINGS pSettings;
1906 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
1907 if (RT_SUCCESS(rc))
1908 {
1909 if (RTFileExists(szFile))
1910 rc = scmSettingsLoadFile(pSettings, szFile);
1911 if (RT_SUCCESS(rc))
1912 {
1913 scmSettingsStackPush(ppSettingsStack, pSettings);
1914 return VINF_SUCCESS;
1915 }
1916
1917 scmSettingsDestroy(pSettings);
1918 }
1919 }
1920 return rc;
1921}
1922
1923
1924/**
1925 * Pops a settings set off the stack.
1926 *
1927 * @returns The popped setttings.
1928 * @param ppSettingsStack The pointer to the pointer to the top stack
1929 * element. This will be used as input and output.
1930 */
1931static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
1932{
1933 PSCMSETTINGS pRet = *ppSettingsStack;
1934 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
1935 *ppSettingsStack = pNew;
1936 if (pNew)
1937 pNew->pUp = NULL;
1938 if (pRet)
1939 {
1940 pRet->pUp = NULL;
1941 pRet->pDown = NULL;
1942 }
1943 return pRet;
1944}
1945
1946/**
1947 * Pops and destroys the top entry of the stack.
1948 *
1949 * @param ppSettingsStack The pointer to the pointer to the top stack
1950 * element. This will be used as input and output.
1951 */
1952static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
1953{
1954 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
1955}
1956
1957/**
1958 * Constructs the base settings for the specified file name.
1959 *
1960 * @returns IPRT status code.
1961 * @param pSettingsStack The top element on the settings stack.
1962 * @param pszFilename The file name.
1963 * @param pszBasename The base name (pointer within @a pszFilename).
1964 * @param cchBasename The length of the base name. (For passing to
1965 * RTStrSimplePatternMultiMatch.)
1966 * @param pBase Base settings to initialize.
1967 */
1968static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
1969 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
1970{
1971 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase(%s, %.*s)\n", pszFilename, cchBasename, pszBasename);
1972
1973 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
1974 if (RT_SUCCESS(rc))
1975 {
1976 /* find the bottom entry in the stack. */
1977 PCSCMSETTINGS pCur = pSettingsStack;
1978 while (pCur->pDown)
1979 pCur = pCur->pDown;
1980
1981 /* Work our way up thru the stack and look for matching pairs. */
1982 while (pCur)
1983 {
1984 size_t const cPairs = pCur->cPairs;
1985 if (cPairs)
1986 {
1987 for (size_t i = 0; i < cPairs; i++)
1988 if ( !pCur->paPairs[i].fMultiPattern
1989 ? RTStrSimplePatternNMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1990 pszBasename, cchBasename)
1991 || RTStrSimplePatternMatch(pCur->paPairs[i].pszPattern, pszFilename)
1992 : RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1993 pszBasename, cchBasename, NULL)
1994 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1995 pszFilename, RTSTR_MAX, NULL))
1996 {
1997 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase: Matched '%s' : '%s'\n",
1998 pCur->paPairs[i].pszPattern, pCur->paPairs[i].pszOptions);
1999 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions,
2000 pCur->paPairs[i].pszRelativeTo, strlen(pCur->paPairs[i].pszRelativeTo));
2001 if (RT_FAILURE(rc))
2002 break;
2003 }
2004 if (RT_FAILURE(rc))
2005 break;
2006 }
2007
2008 /* advance */
2009 pCur = pCur->pUp;
2010 }
2011 }
2012 if (RT_FAILURE(rc))
2013 scmSettingsBaseDelete(pBase);
2014 return rc;
2015}
2016
2017
2018/* -=-=-=-=-=- misc -=-=-=-=-=- */
2019
2020
2021/**
2022 * Prints the per file banner needed and the message level is high enough.
2023 *
2024 * @param pState The rewrite state.
2025 * @param iLevel The required verbosity level.
2026 */
2027void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel)
2028{
2029 if (iLevel <= g_iVerbosity && !pState->fFirst)
2030 {
2031 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
2032 pState->fFirst = true;
2033 }
2034}
2035
2036
2037/**
2038 * Prints a verbose message if the level is high enough.
2039 *
2040 * @param pState The rewrite state. Optional.
2041 * @param iLevel The required verbosity level.
2042 * @param pszFormat The message format string. Can be NULL if we
2043 * only want to trigger the per file message.
2044 * @param ... Format arguments.
2045 */
2046void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
2047{
2048 if (iLevel <= g_iVerbosity)
2049 {
2050 if (pState && !pState->fFirst)
2051 {
2052 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
2053 pState->fFirst = true;
2054 }
2055 RTPrintf(pState
2056 ? "%s: info: "
2057 : "%s: info: ",
2058 g_szProgName);
2059 va_list va;
2060 va_start(va, pszFormat);
2061 RTPrintfV(pszFormat, va);
2062 va_end(va);
2063 }
2064}
2065
2066
2067/**
2068 * Prints an error message.
2069 *
2070 * @returns false
2071 * @param pState The rewrite state. Optional.
2072 * @param rc The error code.
2073 * @param pszFormat The message format string.
2074 * @param ... Format arguments.
2075 */
2076bool ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...)
2077{
2078 if (RT_SUCCESS(pState->rc))
2079 pState->rc = rc;
2080
2081 if (!pState->fFirst)
2082 {
2083 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
2084 pState->fFirst = true;
2085 }
2086 va_list va;
2087 va_start(va, pszFormat);
2088 RTPrintf("%s: error: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &va);
2089 va_end(va);
2090
2091 return false;
2092}
2093
2094
2095/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
2096
2097
2098/**
2099 * Processes a file.
2100 *
2101 * @returns IPRT status code.
2102 * @param pState The rewriter state.
2103 * @param pszFilename The file name.
2104 * @param pszBasename The base name (pointer within @a pszFilename).
2105 * @param cchBasename The length of the base name. (For passing to
2106 * RTStrSimplePatternMultiMatch.)
2107 * @param pBaseSettings The base settings to use. It's OK to modify
2108 * these.
2109 */
2110static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
2111 PSCMSETTINGSBASE pBaseSettings)
2112{
2113 /*
2114 * Do the file level filtering.
2115 */
2116 if ( pBaseSettings->pszFilterFiles
2117 && *pBaseSettings->pszFilterFiles
2118 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
2119 {
2120 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
2121 g_cFilesSkipped++;
2122 return VINF_SUCCESS;
2123 }
2124 if ( pBaseSettings->pszFilterOutFiles
2125 && *pBaseSettings->pszFilterOutFiles
2126 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
2127 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
2128 {
2129 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
2130 g_cFilesSkipped++;
2131 return VINF_SUCCESS;
2132 }
2133 if ( pBaseSettings->fOnlySvnFiles
2134 && !ScmSvnIsInWorkingCopy(pState))
2135 {
2136 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
2137 g_cFilesNotInSvn++;
2138 return VINF_SUCCESS;
2139 }
2140
2141 /*
2142 * Create an input stream from the file and check that it's text.
2143 */
2144 SCMSTREAM Stream1;
2145 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
2146 if (RT_FAILURE(rc))
2147 {
2148 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
2149 return rc;
2150 }
2151 bool const fIsText = ScmStreamIsText(&Stream1);
2152
2153 /*
2154 * Try find a matching rewrite config for this filename.
2155 */
2156 PCSCMCFGENTRY pCfg = pBaseSettings->pTreatAs;
2157 if (!pCfg)
2158 {
2159 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2160 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
2161 {
2162 pCfg = &g_aConfigs[iCfg];
2163 break;
2164 }
2165 if (!pCfg)
2166 {
2167 /* On failure try check for hash-bang stuff before giving up. */
2168 if (fIsText)
2169 {
2170 SCMEOL enmIgn;
2171 size_t cchFirst;
2172 const char *pchFirst = ScmStreamGetLine(&Stream1, &cchFirst, &enmIgn);
2173 if (cchFirst >= 9 && pchFirst && *pchFirst == '#')
2174 {
2175 do
2176 {
2177 pchFirst++;
2178 cchFirst--;
2179 } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst));
2180 if (*pchFirst == '!')
2181 {
2182 do
2183 {
2184 pchFirst++;
2185 cchFirst--;
2186 } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst));
2187 const char *pszTreatAs = NULL;
2188 if ( (cchFirst >= 7 && strncmp(pchFirst, "/bin/sh", 7) == 0)
2189 || (cchFirst >= 9 && strncmp(pchFirst, "/bin/bash", 9) == 0)
2190 || (cchFirst >= 4+9 && strncmp(pchFirst, "/usr/bin/bash", 4+9) == 0) )
2191 pszTreatAs = "shell";
2192 else if ( (cchFirst >= 15 && strncmp(pchFirst, "/usr/bin/python", 15) == 0)
2193 || (cchFirst >= 19 && strncmp(pchFirst, "/usr/bin/env python", 19) == 0) )
2194 pszTreatAs = "python";
2195 else if ( (cchFirst >= 13 && strncmp(pchFirst, "/usr/bin/perl", 13) == 0)
2196 || (cchFirst >= 17 && strncmp(pchFirst, "/usr/bin/env perl", 17) == 0) )
2197 pszTreatAs = "perl";
2198 if (pszTreatAs)
2199 {
2200 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2201 if (strcmp(pszTreatAs, g_aConfigs[iCfg].pszName) == 0)
2202 {
2203 pCfg = &g_aConfigs[iCfg];
2204 break;
2205 }
2206 Assert(pCfg);
2207 }
2208 }
2209 }
2210 ScmStreamRewindForReading(&Stream1);
2211 }
2212 if (!pCfg)
2213 {
2214 ScmVerbose(NULL, 2, "skipping '%s': no rewriters configured\n", pszFilename);
2215 g_cFilesNoRewriters++;
2216 ScmStreamDelete(&Stream1);
2217 return VINF_SUCCESS;
2218 }
2219 }
2220 ScmVerbose(pState, 4, "matched \"%s\" (%s)\n", pCfg->pszFilePattern, pCfg->pszName);
2221 }
2222 else
2223 ScmVerbose(pState, 4, "treat-as \"%s\"\n", pCfg->pszName);
2224
2225 if (fIsText || pCfg->fBinary)
2226 {
2227 ScmVerboseBanner(pState, 3);
2228
2229 /*
2230 * Gather SCM and editor settings from the stream.
2231 */
2232 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
2233 if (RT_SUCCESS(rc))
2234 {
2235 ScmStreamRewindForReading(&Stream1);
2236
2237 /*
2238 * Create two more streams for output and push the text thru all the
2239 * rewriters, switching the two streams around when something is
2240 * actually rewritten. Stream1 remains unchanged.
2241 */
2242 SCMSTREAM Stream2;
2243 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
2244 if (RT_SUCCESS(rc))
2245 {
2246 SCMSTREAM Stream3;
2247 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
2248 if (RT_SUCCESS(rc))
2249 {
2250 bool fModified = false;
2251 PSCMSTREAM pIn = &Stream1;
2252 PSCMSTREAM pOut = &Stream2;
2253 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
2254 {
2255 pState->rc = VINF_SUCCESS;
2256 bool fRc = pCfg->paRewriters[iRw]->pfnRewriter(pState, pIn, pOut, pBaseSettings);
2257 if (RT_FAILURE(pState->rc))
2258 break;
2259 if (fRc)
2260 {
2261 PSCMSTREAM pTmp = pOut;
2262 pOut = pIn == &Stream1 ? &Stream3 : pIn;
2263 pIn = pTmp;
2264 fModified = true;
2265 }
2266
2267 ScmStreamRewindForReading(pIn);
2268 ScmStreamRewindForWriting(pOut);
2269 }
2270
2271 rc = pState->rc;
2272 if (RT_SUCCESS(rc))
2273 {
2274 rc = ScmStreamGetStatus(&Stream1);
2275 if (RT_SUCCESS(rc))
2276 rc = ScmStreamGetStatus(&Stream2);
2277 if (RT_SUCCESS(rc))
2278 rc = ScmStreamGetStatus(&Stream3);
2279 if (RT_SUCCESS(rc))
2280 {
2281 /*
2282 * If rewritten, write it back to disk.
2283 */
2284 if (fModified && !pCfg->fBinary)
2285 {
2286 if (!g_fDryRun)
2287 {
2288 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
2289 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
2290 if (RT_FAILURE(rc))
2291 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
2292 }
2293 else
2294 {
2295 ScmVerboseBanner(pState, 1);
2296 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol,
2297 g_fDiffIgnoreLeadingWS, g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars,
2298 pBaseSettings->cchTab, g_pStdOut);
2299 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n",
2300 pszFilename, g_pszChangedSuff);
2301 }
2302 g_cFilesModified++;
2303 }
2304 else if (fModified)
2305 rc = RTMsgErrorRc(VERR_INTERNAL_ERROR, "Rewriters modified binary file! Impossible!");
2306
2307 /*
2308 * If pending SVN property changes, apply them.
2309 */
2310 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
2311 {
2312 if (!g_fDryRun)
2313 {
2314 rc = ScmSvnApplyChanges(pState);
2315 if (RT_FAILURE(rc))
2316 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
2317 }
2318 else
2319 ScmSvnDisplayChanges(pState);
2320 if (!fModified)
2321 g_cFilesModified++;
2322 }
2323
2324 if (!fModified && !pState->cSvnPropChanges)
2325 ScmVerbose(pState, 3, "%s: no change\n", pszFilename);
2326 }
2327 else
2328 RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
2329 }
2330 ScmStreamDelete(&Stream3);
2331 }
2332 else
2333 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2334 ScmStreamDelete(&Stream2);
2335 }
2336 else
2337 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2338 }
2339 else
2340 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
2341 }
2342 else
2343 {
2344 ScmVerbose(pState, 2, "not text file: \"%s\"\n", pszFilename);
2345 g_cFilesBinaries++;
2346 }
2347 ScmStreamDelete(&Stream1);
2348
2349 return rc;
2350}
2351
2352/**
2353 * Processes a file.
2354 *
2355 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
2356 * directory recursion method.
2357 *
2358 * @returns IPRT status code.
2359 * @param pszFilename The file name.
2360 * @param pszBasename The base name (pointer within @a pszFilename).
2361 * @param cchBasename The length of the base name. (For passing to
2362 * RTStrSimplePatternMultiMatch.)
2363 * @param pSettingsStack The settings stack (pointer to the top element).
2364 */
2365static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
2366 PSCMSETTINGS pSettingsStack)
2367{
2368 SCMSETTINGSBASE Base;
2369 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
2370 if (RT_SUCCESS(rc))
2371 {
2372 SCMRWSTATE State;
2373 State.pszFilename = pszFilename;
2374 State.fFirst = false;
2375 State.fIsInSvnWorkingCopy = 0;
2376 State.cSvnPropChanges = 0;
2377 State.paSvnPropChanges = NULL;
2378 State.rc = VINF_SUCCESS;
2379
2380 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
2381
2382 size_t i = State.cSvnPropChanges;
2383 while (i-- > 0)
2384 {
2385 RTStrFree(State.paSvnPropChanges[i].pszName);
2386 RTStrFree(State.paSvnPropChanges[i].pszValue);
2387 }
2388 RTMemFree(State.paSvnPropChanges);
2389
2390 scmSettingsBaseDelete(&Base);
2391
2392 g_cFilesProcessed++;
2393 }
2394 return rc;
2395}
2396
2397/**
2398 * Tries to correct RTDIRENTRY_UNKNOWN.
2399 *
2400 * @returns Corrected type.
2401 * @param pszPath The path to the object in question.
2402 */
2403static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
2404{
2405 RTFSOBJINFO Info;
2406 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
2407 if (RT_FAILURE(rc))
2408 return RTDIRENTRYTYPE_UNKNOWN;
2409 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
2410 return RTDIRENTRYTYPE_DIRECTORY;
2411 if (RTFS_IS_FILE(Info.Attr.fMode))
2412 return RTDIRENTRYTYPE_FILE;
2413 return RTDIRENTRYTYPE_UNKNOWN;
2414}
2415
2416/**
2417 * Recurse into a sub-directory and process all the files and directories.
2418 *
2419 * @returns IPRT status code.
2420 * @param pszBuf Path buffer containing the directory path on
2421 * entry. This ends with a dot. This is passed
2422 * along when recursing in order to save stack space
2423 * and avoid needless copying.
2424 * @param cchDir Length of our path in pszbuf.
2425 * @param pEntry Directory entry buffer. This is also passed
2426 * along when recursing to save stack space.
2427 * @param pSettingsStack The settings stack (pointer to the top element).
2428 * @param iRecursion The recursion depth. This is used to restrict
2429 * the recursions.
2430 */
2431static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
2432 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
2433{
2434 int rc;
2435 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
2436
2437 /*
2438 * Make sure we stop somewhere.
2439 */
2440 if (iRecursion > 128)
2441 {
2442 RTMsgError("recursion too deep: %d\n", iRecursion);
2443 return VINF_SUCCESS; /* ignore */
2444 }
2445
2446 /*
2447 * Check if it's excluded by --only-svn-dir.
2448 */
2449 if (pSettingsStack->Base.fOnlySvnDirs)
2450 {
2451 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
2452 return VINF_SUCCESS;
2453 }
2454 g_cDirsProcessed++;
2455
2456 /*
2457 * Try open and read the directory.
2458 */
2459 RTDIR hDir;
2460 rc = RTDirOpenFiltered(&hDir, pszBuf, RTDIRFILTER_NONE, 0 /*fFlags*/);
2461 if (RT_FAILURE(rc))
2462 {
2463 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
2464 return rc;
2465 }
2466 for (;;)
2467 {
2468 /* Read the next entry. */
2469 rc = RTDirRead(hDir, pEntry, NULL);
2470 if (RT_FAILURE(rc))
2471 {
2472 if (rc == VERR_NO_MORE_FILES)
2473 rc = VINF_SUCCESS;
2474 else
2475 RTMsgError("RTDirRead -> %Rrc\n", rc);
2476 break;
2477 }
2478
2479 /* Skip '.' and '..'. */
2480 if ( pEntry->szName[0] == '.'
2481 && ( pEntry->cbName == 1
2482 || ( pEntry->cbName == 2
2483 && pEntry->szName[1] == '.')))
2484 continue;
2485
2486 /* Enter it into the buffer so we've got a full name to work
2487 with when needed. */
2488 if (pEntry->cbName + cchDir >= RTPATH_MAX)
2489 {
2490 RTMsgError("Skipping too long entry: %s", pEntry->szName);
2491 continue;
2492 }
2493 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
2494
2495 /* Figure the type. */
2496 RTDIRENTRYTYPE enmType = pEntry->enmType;
2497 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
2498 enmType = scmFigureUnknownType(pszBuf);
2499
2500 /* Process the file or directory, skip the rest. */
2501 if (enmType == RTDIRENTRYTYPE_FILE)
2502 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
2503 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
2504 {
2505 /* Append the dot for the benefit of the pattern matching. */
2506 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
2507 {
2508 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
2509 continue;
2510 }
2511 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
2512 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
2513
2514 if ( !pSettingsStack->Base.pszFilterOutDirs
2515 || !*pSettingsStack->Base.pszFilterOutDirs
2516 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
2517 pEntry->szName, pEntry->cbName, NULL)
2518 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
2519 pszBuf, cchSubDir, NULL)
2520 )
2521 )
2522 {
2523 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
2524 if (RT_SUCCESS(rc))
2525 {
2526 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
2527 scmSettingsStackPopAndDestroy(&pSettingsStack);
2528 }
2529 }
2530 }
2531 if (RT_FAILURE(rc))
2532 break;
2533 }
2534 RTDirClose(hDir);
2535 return rc;
2536
2537}
2538
2539/**
2540 * Process a directory tree.
2541 *
2542 * @returns IPRT status code.
2543 * @param pszDir The directory to start with. This is pointer to
2544 * a RTPATH_MAX sized buffer.
2545 */
2546static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
2547{
2548 /*
2549 * Setup the recursion.
2550 */
2551 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
2552 if (RT_SUCCESS(rc))
2553 {
2554 RTPathChangeToUnixSlashes(pszDir, true);
2555
2556 RTDIRENTRY Entry;
2557 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
2558 }
2559 else
2560 RTMsgError("RTPathAppend: %Rrc\n", rc);
2561 return rc;
2562}
2563
2564
2565/**
2566 * Processes a file or directory specified as an command line argument.
2567 *
2568 * @returns IPRT status code
2569 * @param pszSomething What we found in the command line arguments.
2570 * @param pSettingsStack The settings stack (pointer to the top element).
2571 */
2572static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
2573{
2574 char szBuf[RTPATH_MAX];
2575 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
2576 if (RT_SUCCESS(rc))
2577 {
2578 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
2579
2580 PSCMSETTINGS pSettings;
2581 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
2582 if (RT_SUCCESS(rc))
2583 {
2584 scmSettingsStackPush(&pSettingsStack, pSettings);
2585
2586 if (RTFileExists(szBuf))
2587 {
2588 const char *pszBasename = RTPathFilename(szBuf);
2589 if (pszBasename)
2590 {
2591 size_t cchBasename = strlen(pszBasename);
2592 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
2593 }
2594 else
2595 {
2596 RTMsgError("RTPathFilename: NULL\n");
2597 rc = VERR_IS_A_DIRECTORY;
2598 }
2599 }
2600 else
2601 rc = scmProcessDirTree(szBuf, pSettingsStack);
2602
2603 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
2604 Assert(pPopped == pSettings); RT_NOREF_PV(pPopped);
2605 scmSettingsDestroy(pSettings);
2606 }
2607 else
2608 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
2609 }
2610 else
2611 RTMsgError("RTPathAbs: %Rrc\n", rc);
2612 return rc;
2613}
2614
2615/**
2616 * Print some stats.
2617 */
2618static void scmPrintStats(void)
2619{
2620 ScmVerbose(NULL, 0,
2621 g_fDryRun
2622 ? "%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"
2623 : "%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",
2624 g_cFilesModified,
2625 g_cFilesProcessed, g_cFilesProcessed == 1 ? "" : "s",
2626 g_cDirsProcessed, g_cDirsProcessed == 1 ? "" : "s",
2627 g_cFilesNoRewriters, g_cFilesNoRewriters == 1 ? "" : "s",
2628 g_cFilesBinaries, g_cFilesBinaries == 1 ? "y" : "ies",
2629 g_cFilesNotInSvn, g_cFilesSkipped);
2630}
2631
2632/**
2633 * Display the rewriter actions.
2634 *
2635 * @returns RTEXITCODE_SUCCESS.
2636 */
2637static int scmHelpActions(void)
2638{
2639 RTPrintf("Available rewriter actions:\n");
2640 for (uint32_t i = 0; i < RT_ELEMENTS(g_papRewriterActions); i++)
2641 RTPrintf(" %s\n", g_papRewriterActions[i]->pszName);
2642 return RTEXITCODE_SUCCESS;
2643}
2644
2645/**
2646 * Display the default configuration.
2647 *
2648 * @returns RTEXITCODE_SUCCESS.
2649 */
2650static int scmHelpConfig(void)
2651{
2652 RTPrintf("Rewriter configuration:\n");
2653 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2654 {
2655 RTPrintf("\n %s%s - %s:\n",
2656 g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].fBinary ? " (binary)" : "", g_aConfigs[iCfg].pszFilePattern);
2657 for (size_t i = 0; i < g_aConfigs[iCfg].cRewriters; i++)
2658 RTPrintf(" %s\n", g_aConfigs[iCfg].paRewriters[i]->pszName);
2659 }
2660 return RTEXITCODE_SUCCESS;
2661}
2662
2663/**
2664 * Display the primary help text.
2665 *
2666 * @returns RTEXITCODE_SUCCESS.
2667 * @param paOpts Options.
2668 * @param cOpts Number of options.
2669 */
2670static int scmHelp(PCRTGETOPTDEF paOpts, size_t cOpts)
2671{
2672 RTPrintf("VirtualBox Source Code Massager\n"
2673 "\n"
2674 "Usage: %s [options] <files & dirs>\n"
2675 "\n"
2676 "General options:\n", g_szProgName);
2677 for (size_t i = 0; i < cOpts; i++)
2678 {
2679 /* Grouping. */
2680 switch (paOpts[i].iShort)
2681 {
2682 case SCMOPT_DIFF_IGNORE_EOL:
2683 RTPrintf("\nDiff options (dry runs):\n");
2684 break;
2685 case SCMOPT_CONVERT_EOL:
2686 RTPrintf("\nRewriter action options:\n");
2687 break;
2688 case SCMOPT_ONLY_SVN_DIRS:
2689 RTPrintf("\nInput selection options:\n");
2690 break;
2691 case SCMOPT_TREAT_AS:
2692 RTPrintf("\nMisc options:\n");
2693 break;
2694 }
2695
2696 size_t cExtraAdvance = 0;
2697 if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
2698 {
2699 cExtraAdvance = i + 1 < cOpts
2700 && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL
2701 || strstr(paOpts[i+1].pszLong, "-not-") != NULL
2702 || strstr(paOpts[i+1].pszLong, "-dont-") != NULL
2703 || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v')
2704 || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D')
2705 );
2706 if (cExtraAdvance)
2707 RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong);
2708 else if (paOpts[i].iShort != SCMOPT_NO_UPDATE_LICENSE)
2709 RTPrintf(" %s\n", paOpts[i].pszLong);
2710 else
2711 {
2712 RTPrintf(" %s,\n"
2713 " %s,\n"
2714 " %s,\n"
2715 " %s,\n"
2716 " %s,\n"
2717 " %s,\n"
2718 " %s\n",
2719 paOpts[i].pszLong,
2720 paOpts[i + 1].pszLong,
2721 paOpts[i + 2].pszLong,
2722 paOpts[i + 3].pszLong,
2723 paOpts[i + 4].pszLong,
2724 paOpts[i + 5].pszLong,
2725 paOpts[i + 6].pszLong);
2726 cExtraAdvance = 6;
2727 }
2728 }
2729 else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
2730 switch (paOpts[i].iShort)
2731 {
2732 case SCMOPT_DEL_ACTION:
2733 RTPrintf(" %s pattern\n", paOpts[i].pszLong);
2734 break;
2735 case SCMOPT_FILTER_OUT_DIRS:
2736 case SCMOPT_FILTER_FILES:
2737 case SCMOPT_FILTER_OUT_FILES:
2738 RTPrintf(" %s multi-pattern\n", paOpts[i].pszLong);
2739 break;
2740 default:
2741 RTPrintf(" %s string\n", paOpts[i].pszLong);
2742 }
2743 else
2744 RTPrintf(" %s value\n", paOpts[i].pszLong);
2745 switch (paOpts[i].iShort)
2746 {
2747 case 'd':
2748 case 'D': RTPrintf(" Default: --dry-run\n"); break;
2749 case SCMOPT_CHECK_RUN: RTPrintf(" Default: --dry-run\n"); break;
2750 case 'f': RTPrintf(" Default: none\n"); break;
2751 case 'q':
2752 case 'v': RTPrintf(" Default: -vv\n"); break;
2753 case SCMOPT_HELP_CONFIG: RTPrintf(" Shows the standard file rewriter configurations.\n"); break;
2754 case SCMOPT_HELP_ACTIONS: RTPrintf(" Shows the available rewriter actions.\n"); break;
2755
2756 case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break;
2757 case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break;
2758 case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break;
2759 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break;
2760 case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break;
2761
2762 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
2763 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
2764 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
2765 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
2766 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
2767 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
2768 case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break;
2769 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break;
2770
2771 case SCMOPT_FIX_HEADER_GUARDS:
2772 RTPrintf(" Fix header guards and #pragma once. Default: %RTbool\n", g_Defaults.fFixHeaderGuards);
2773 break;
2774 case SCMOPT_PRAGMA_ONCE:
2775 RTPrintf(" Whether to include #pragma once with the header guard. Default: %RTbool\n", g_Defaults.fPragmaOnce);
2776 break;
2777 case SCMOPT_ENDIF_GUARD_COMMENT:
2778 RTPrintf(" Put a comment on the header guard #endif or not. Default: %RTbool\n", g_Defaults.fEndifGuardComment);
2779 break;
2780 case SCMOPT_GUARD_RELATIVE_TO_DIR:
2781 RTPrintf(" Header guard should be normalized relative to given dir.\n"
2782 " If empty no normalization takes place. Default: '%s'\n", g_Defaults.pszGuardRelativeToDir);
2783 break;
2784 case SCMOPT_GUARD_PREFIX:
2785 RTPrintf(" Prefix to use with --guard-relative-to-dir. Default: %s\n", g_Defaults.pszGuardPrefix);
2786 break;
2787 case SCMOPT_FIX_TODOS:
2788 RTPrintf(" Fix @todo statements so doxygen sees them. Default: %RTbool\n", g_Defaults.fFixTodos);
2789 break;
2790 case SCMOPT_FIX_ERR_H:
2791 RTPrintf(" Fix err.h/errcore.h usage. Default: %RTbool\n", g_Defaults.fFixErrH);
2792 break;
2793 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
2794 RTPrintf(" Update the copyright year. Default: %RTbool\n", g_Defaults.fUpdateCopyrightYear);
2795 break;
2796 case SCMOPT_EXTERNAL_COPYRIGHT:
2797 RTPrintf(" Only external copyright holders. Default: %RTbool\n", g_Defaults.fExternalCopyright);
2798 break;
2799 case SCMOPT_NO_UPDATE_LICENSE:
2800 RTPrintf(" License selection. Default: --license-ose-gpl\n");
2801 break;
2802
2803 case SCMOPT_LGPL_DISCLAIMER:
2804 RTPrintf(" Include LGPL version disclaimer. Default: --no-lgpl-disclaimer\n");
2805 break;
2806
2807 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
2808 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
2809 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
2810 case SCMOPT_SKIP_SVN_SYNC_PROCESS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSkipSvnSyncProcess); break;
2811 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
2812 case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break;
2813
2814 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
2815 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
2816 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
2817 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
2818 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
2819
2820 case SCMOPT_TREAT_AS:
2821 RTPrintf(" For treat the input file(s) differently, restting any --add-action.\n"
2822 " If the value is empty defaults will be used again. Possible values:\n");
2823 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2824 RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern);
2825 break;
2826
2827 case SCMOPT_ADD_ACTION:
2828 RTPrintf(" Adds a rewriter action. The first use after a --treat-as will copy and\n"
2829 " the action list selected by the --treat-as. The actuion list will be\n"
2830 " flushed by --treat-as.\n");
2831 break;
2832
2833 case SCMOPT_DEL_ACTION:
2834 RTPrintf(" Deletes one or more rewriter action (pattern). Best used after\n"
2835 " a --treat-as.\n");
2836 break;
2837
2838 default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong));
2839 }
2840 i += cExtraAdvance;
2841 }
2842
2843 return RTEXITCODE_SUCCESS;
2844}
2845
2846int main(int argc, char **argv)
2847{
2848 int rc = RTR3InitExe(argc, &argv, 0);
2849 if (RT_FAILURE(rc))
2850 return 1;
2851
2852 /*
2853 * Init the current year.
2854 */
2855 RTTIMESPEC Now;
2856 RTTIME Time;
2857 RTTimeExplode(&Time, RTTimeNow(&Now));
2858 g_uYear = Time.i32Year;
2859
2860 /*
2861 * Init the settings.
2862 */
2863 PSCMSETTINGS pSettings;
2864 rc = scmSettingsCreate(&pSettings, &g_Defaults);
2865 if (RT_FAILURE(rc))
2866 {
2867 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
2868 return 1;
2869 }
2870
2871 /*
2872 * Parse arguments and process input in order (because this is the only
2873 * thing that works at the moment).
2874 */
2875 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
2876 {
2877 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
2878 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
2879 { "--check-run", SCMOPT_CHECK_RUN, RTGETOPT_REQ_NOTHING },
2880 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
2881 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2882 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2883 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
2884 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
2885 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
2886 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
2887 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
2888 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
2889 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
2890 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
2891 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
2892 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
2893 };
2894 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
2895
2896 bool fCheckRun = false;
2897 RTGETOPTUNION ValueUnion;
2898 RTGETOPTSTATE GetOptState;
2899 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2900 AssertReleaseRCReturn(rc, 1);
2901
2902 while ( (rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0
2903 && rc != VINF_GETOPT_NOT_OPTION)
2904 {
2905 switch (rc)
2906 {
2907 case 'd':
2908 g_fDryRun = true;
2909 fCheckRun = false;
2910 break;
2911 case 'D':
2912 g_fDryRun = fCheckRun = false;
2913 break;
2914 case SCMOPT_CHECK_RUN:
2915 g_fDryRun = fCheckRun = true;
2916 break;
2917
2918 case 'f':
2919 g_pszFileFilter = ValueUnion.psz;
2920 break;
2921
2922 case 'h':
2923 return scmHelp(s_aOpts, RT_ELEMENTS(s_aOpts));
2924
2925 case SCMOPT_HELP_CONFIG:
2926 return scmHelpConfig();
2927
2928 case SCMOPT_HELP_ACTIONS:
2929 return scmHelpActions();
2930
2931 case 'q':
2932 g_iVerbosity = 0;
2933 break;
2934
2935 case 'v':
2936 g_iVerbosity++;
2937 break;
2938
2939 case 'V':
2940 {
2941 /* The following is assuming that svn does it's job here. */
2942 static const char s_szRev[] = "$Revision: 76551 $";
2943 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
2944 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
2945 return 0;
2946 }
2947
2948 case SCMOPT_DIFF_IGNORE_EOL:
2949 g_fDiffIgnoreEol = true;
2950 break;
2951 case SCMOPT_DIFF_NO_IGNORE_EOL:
2952 g_fDiffIgnoreEol = false;
2953 break;
2954
2955 case SCMOPT_DIFF_IGNORE_SPACE:
2956 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
2957 break;
2958 case SCMOPT_DIFF_NO_IGNORE_SPACE:
2959 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
2960 break;
2961
2962 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
2963 g_fDiffIgnoreLeadingWS = true;
2964 break;
2965 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
2966 g_fDiffIgnoreLeadingWS = false;
2967 break;
2968
2969 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
2970 g_fDiffIgnoreTrailingWS = true;
2971 break;
2972 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
2973 g_fDiffIgnoreTrailingWS = false;
2974 break;
2975
2976 case SCMOPT_DIFF_SPECIAL_CHARS:
2977 g_fDiffSpecialChars = true;
2978 break;
2979 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
2980 g_fDiffSpecialChars = false;
2981 break;
2982
2983 default:
2984 {
2985 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion, "/", 1);
2986 if (RT_SUCCESS(rc2))
2987 break;
2988 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
2989 return 2;
2990 return RTGetOptPrintError(rc, &ValueUnion);
2991 }
2992 }
2993 }
2994
2995 /*
2996 * Process non-options.
2997 */
2998 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2999 if (rc == VINF_GETOPT_NOT_OPTION)
3000 {
3001 ScmSvnInit();
3002
3003 bool fWarned = g_fDryRun;
3004 while (rc == VINF_GETOPT_NOT_OPTION)
3005 {
3006 if (!fWarned)
3007 {
3008 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
3009 "%s: there is a slight risk that bugs or a full disk may cause\n"
3010 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
3011 "%s: all your changes already. If you didn't, then don't blame\n"
3012 "%s: anyone for not warning you!\n"
3013 "%s:\n"
3014 "%s: Press any key to continue...\n",
3015 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
3016 g_szProgName, g_szProgName);
3017 RTStrmGetCh(g_pStdIn);
3018 fWarned = true;
3019 }
3020
3021 rc = scmProcessSomething(ValueUnion.psz, pSettings);
3022 if (RT_FAILURE(rc))
3023 {
3024 rcExit = RTEXITCODE_FAILURE;
3025 break;
3026 }
3027
3028 /* next */
3029 rc = RTGetOpt(&GetOptState, &ValueUnion);
3030 if (RT_FAILURE(rc))
3031 rcExit = RTGetOptPrintError(rc, &ValueUnion);
3032 }
3033
3034 scmPrintStats();
3035 ScmSvnTerm();
3036 }
3037 else
3038 RTMsgWarning("No files or directories specified. Doing nothing");
3039
3040 scmSettingsDestroy(pSettings);
3041
3042 /* If we're in checking mode, fail if any files needed modification. */
3043 if ( rcExit == RTEXITCODE_SUCCESS
3044 && fCheckRun
3045 && g_cFilesModified > 0)
3046 {
3047 RTMsgError("Checking mode failed! %u file%s needs modifications", g_cFilesBinaries, g_cFilesBinaries > 1 ? "s" : "");
3048 rcExit = RTEXITCODE_FAILURE;
3049 }
3050
3051 return rcExit;
3052}
3053
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