VirtualBox

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

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

scm: Made default guard prefix VBOX_INCLUDED_SRC_.

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