VirtualBox

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

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

scm,asm*watcom*.h: Added scm option to omit the #pragma once for the multipass watcom inline assembly files. bugref:9344

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