VirtualBox

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

Last change on this file since 93553 was 93553, checked in by vboxsync, 3 years ago

scm: Added checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK. Also added a new error reporter for stuff that needs manual fixing. bugref:9898

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