VirtualBox

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

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

scm: Check UTF-8 encoding and reject files with bidirectional control codes in them.

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