VirtualBox

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

Last change on this file since 69462 was 69462, checked in by vboxsync, 8 years ago

scm: Added some more flexibility wrt config as well as generic text and binary rewriters (latter just fiddles svn attributes).

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette