VirtualBox

source: vbox/trunk/src/bldprogs/scmrw.cpp@ 74094

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

scm: fix for = and == priorities

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 94.6 KB
Line 
1/* $Id: scmrw.cpp 73075 2018-07-11 16:24:32Z 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
40
41/*********************************************************************************************************************************
42* Structures and Typedefs *
43*********************************************************************************************************************************/
44/** License types. */
45typedef enum SCMLICENSETYPE
46{
47 kScmLicenseType_Invalid = 0,
48 kScmLicenseType_OseGpl,
49 kScmLicenseType_OseDualGplCddl,
50 kScmLicenseType_OseCddl,
51 kScmLicenseType_VBoxLgpl,
52 kScmLicenseType_Mit,
53 kScmLicenseType_Confidential
54} SCMLICENSETYPE;
55
56/** A license. */
57typedef struct SCMLICENSETEXT
58{
59 /** The license type. */
60 SCMLICENSETYPE enmType;
61 /** The license option. */
62 SCMLICENSE enmOpt;
63 /** The license text. */
64 const char *psz;
65 /** The license text length. */
66 size_t cch;
67} SCMLICENSETEXT;
68/** Pointer to a license. */
69typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
70
71/**
72 * Copyright + license rewriter state.
73 */
74typedef struct SCMCOPYRIGHTINFO
75{
76 /** State. */
77 PSCMRWSTATE pState; /**< input */
78 /** The comment style (neede for C/C++). */
79 SCMCOMMENTSTYLE enmCommentStyle; /**< input */
80
81 /** Number of comments we've parsed. */
82 uint32_t cComments;
83
84 /** Copy of the contributed-by line if present. */
85 char *pszContributedBy;
86
87 /** @name Common info
88 * @{ */
89 uint32_t iLineComment;
90 uint32_t cLinesComment; /**< This excludes any external license lines. */
91 /** @} */
92
93 /** @name Copyright info
94 * @{ */
95 uint32_t iLineCopyright;
96 uint32_t uFirstYear;
97 uint32_t uLastYear;
98 bool fWellFormedCopyright;
99 bool fUpToDateCopyright;
100 /** @} */
101
102 /** @name License info
103 * @{ */
104 bool fOpenSource; /**< input */
105 PCSCMLICENSETEXT pExpectedLicense; /**< input */
106 PCSCMLICENSETEXT paLicenses; /**< input */
107 SCMLICENSE enmLicenceOpt; /**< input */
108 uint32_t iLineLicense;
109 uint32_t cLinesLicense;
110 PCSCMLICENSETEXT pCurrentLicense;
111 bool fIsCorrectLicense;
112 bool fWellFormedLicense;
113 bool fExternalLicense;
114 /** @} */
115
116 /** @name LGPL licence notice and disclaimer info
117 * @{ */
118 /** Wheter to check for LGPL license notices and disclaimers. */
119 bool fCheckforLgpl;
120 /** The approximate line we found the (first) LGPL licence notice on. */
121 uint32_t iLineLgplNotice;
122 /** The line number after the LGPL notice comment. */
123 uint32_t iLineAfterLgplComment;
124 /** The LGPL disclaimer line. */
125 uint32_t iLineLgplDisclaimer;
126 /** @} */
127
128} SCMCOPYRIGHTINFO;
129typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
130
131
132/*********************************************************************************************************************************
133* Global Variables *
134*********************************************************************************************************************************/
135/** --license-ose-gpl */
136static const char g_szVBoxOseGpl[] =
137 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
138 "available from http://www.virtualbox.org. This file is free software;\n"
139 "you can redistribute it and/or modify it under the terms of the GNU\n"
140 "General Public License (GPL) as published by the Free Software\n"
141 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
142 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
143 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
144
145/** --license-ose-dual */
146static const char g_szVBoxOseDualGplCddl[] =
147 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
148 "available from http://www.virtualbox.org. This file is free software;\n"
149 "you can redistribute it and/or modify it under the terms of the GNU\n"
150 "General Public License (GPL) as published by the Free Software\n"
151 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
152 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
153 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
154 "\n"
155 "The contents of this file may alternatively be used under the terms\n"
156 "of the Common Development and Distribution License Version 1.0\n"
157 "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
158 "VirtualBox OSE distribution, in which case the provisions of the\n"
159 "CDDL are applicable instead of those of the GPL.\n"
160 "\n"
161 "You may elect to license modified versions of this file under the\n"
162 "terms and conditions of either the GPL or the CDDL or both.\n";
163
164/** --license-ose-cddl */
165static const char g_szVBoxOseCddl[] =
166 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
167 "available from http://www.virtualbox.org. This file is free software;\n"
168 "you can redistribute it and/or modify it under the terms of the Common\n"
169 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
170 "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
171 "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
172 "WITHOUT ANY WARRANTY of any kind.\n";
173
174/** --license-lgpl */
175static const char g_szVBoxLgpl[] =
176 "This file is part of a free software library; you can redistribute\n"
177 "it and/or modify it under the terms of the GNU Lesser General\n"
178 "Public License version 2.1 as published by the Free Software\n"
179 "Foundation and shipped in the \"COPYING\" file with this library.\n"
180 "The library is distributed in the hope that it will be useful,\n"
181 "but WITHOUT ANY WARRANTY of any kind.\n"
182 "\n"
183 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
184 "any license choice other than GPL or LGPL is available it will\n"
185 "apply instead, Oracle elects to use only the Lesser General Public\n"
186 "License version 2.1 (LGPLv2) at this time for any software where\n"
187 "a choice of LGPL license versions is made available with the\n"
188 "language indicating that LGPLv2 or any later version may be used,\n"
189 "or where a choice of which version of the LGPL is applied is\n"
190 "otherwise unspecified.\n";
191
192/** --license-mit
193 * @note This isn't detectable as VirtualBox or Oracle specific.
194 */
195static const char g_szMit[] =
196 "Permission is hereby granted, free of charge, to any person\n"
197 "obtaining a copy of this software and associated documentation\n"
198 "files (the \"Software\"), to deal in the Software without\n"
199 "restriction, including without limitation the rights to use,\n"
200 "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
201 "copies of the Software, and to permit persons to whom the\n"
202 "Software is furnished to do so, subject to the following\n"
203 "conditions:\n"
204 "\n"
205 "The above copyright notice and this permission notice shall be\n"
206 "included in all copies or substantial portions of the Software.\n"
207 "\n"
208 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
209 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
210 "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
211 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
212 "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
213 "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
214 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
215 "OTHER DEALINGS IN THE SOFTWARE.\n";
216
217/** --license-mit, alternative wording \#1.
218 * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
219 * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
220 * couple of lines shorter. */
221static const char g_szMitAlt1[] =
222 "Permission is hereby granted, free of charge, to any person obtaining a\n"
223 "copy of this software and associated documentation files (the \"Software\"),\n"
224 "to deal in the Software without restriction, including without limitation\n"
225 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
226 "and/or sell copies of the Software, and to permit persons to whom the\n"
227 "Software is furnished to do so, subject to the following conditions:\n"
228 "\n"
229 "The above copyright notice and this permission notice shall be included in\n"
230 "all copies or substantial portions of the Software.\n"
231 "\n"
232 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
233 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
234 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
235 "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
236 "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
237 "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
238 "OTHER DEALINGS IN THE SOFTWARE.\n";
239
240/** --license-mit, alternative wording \#2.
241 * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
242 * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
243 * Its layout is wider, so it is a couple of lines shorter. */
244static const char g_szMitAlt2[] =
245 "Permission is hereby granted, free of charge, to any person obtaining a\n"
246 "copy of this software and associated documentation files (the \"Software\"),\n"
247 "to deal in the Software without restriction, including without limitation\n"
248 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
249 "and/or sell copies of the Software, and to permit persons to whom the\n"
250 "Software is furnished to do so, subject to the following conditions:\n"
251 "\n"
252 "The above copyright notice and this permission notice shall be included in\n"
253 "all copies or substantial portions of the Software.\n"
254 "\n"
255 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
256 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
257 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
258 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
259 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
260 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
261 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
262
263/** --license-mit, alternative wording \#3.
264 * @note This differes from g_szMitAlt2 in that the second and third sections
265 * have been switch. */
266static const char g_szMitAlt3[] =
267 "Permission is hereby granted, free of charge, to any person obtaining a\n"
268 "copy of this software and associated documentation files (the \"Software\"),\n"
269 "to deal in the Software without restriction, including without limitation\n"
270 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
271 "and/or sell copies of the Software, and to permit persons to whom the\n"
272 "Software is furnished to do so, subject to the following conditions:\n"
273 "\n"
274 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
275 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
276 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
277 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
278 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
279 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
280 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
281 "\n"
282 "The above copyright notice and this permission notice shall be included in\n"
283 "all copies or substantial portions of the Software.\n";
284
285/** --license-(based-on)mit, alternative wording \#4.
286 * @note This differs from g_szMitAlt2 in injecting "(including the next
287 * paragraph)". */
288static const char g_szMitAlt4[] =
289 "Permission is hereby granted, free of charge, to any person obtaining a\n"
290 "copy of this software and associated documentation files (the \"Software\"),\n"
291 "to deal in the Software without restriction, including without limitation\n"
292 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
293 "and/or sell copies of the Software, and to permit persons to whom the\n"
294 "Software is furnished to do so, subject to the following conditions:\n"
295 "\n"
296 "The above copyright notice and this permission notice (including the next\n"
297 "paragraph) shall be included in all copies or substantial portions of the\n"
298 "Software.\n"
299 "\n"
300 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
301 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
302 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
303 "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
304 "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
305 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
306 "DEALINGS IN THE SOFTWARE.\n";
307
308/** --license-(based-on)mit, alternative wording \#5.
309 * @note This differs from g_szMitAlt3 in using "sub license" instead of
310 * "sublicense" and adding an illogical "(including the next
311 * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
312static const char g_szMitAlt5[] =
313 "Permission is hereby granted, free of charge, to any person obtaining a\n"
314 "copy of this software and associated documentation files (the\n"
315 "\"Software\"), to deal in the Software without restriction, including\n"
316 "without limitation the rights to use, copy, modify, merge, publish,\n"
317 "distribute, sub license, and/or sell copies of the Software, and to\n"
318 "permit persons to whom the Software is furnished to do so, subject to\n"
319 "the following conditions:\n"
320 "\n"
321 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
322 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
323 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
324 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
325 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
326 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
327 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
328 "\n"
329 "The above copyright notice and this permission notice (including the\n"
330 "next paragraph) shall be included in all copies or substantial portions\n"
331 "of the Software.\n";
332
333/** Oracle confidential. */
334static const char g_szOracleConfidential[] =
335 "Oracle Corporation confidential\n"
336 "All rights reserved\n";
337
338/** Licenses to detect when --license-mit isn't used. */
339static const SCMLICENSETEXT g_aLicenses[] =
340{
341 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
342 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
343 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
344 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
345 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
346 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
347};
348
349/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
350static const SCMLICENSETEXT g_aLicensesWithMit[] =
351{
352 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
353 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
354 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
355 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
356 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
357 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
358 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
359 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
360 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
361 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
362 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
363};
364
365/** Copyright holder. */
366static const char g_szCopyrightHolder[] = "Oracle Corporation";
367
368/** LGPL disclaimer. */
369static const char g_szLgplDisclaimer[] =
370 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
371 "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
372 "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
373 "a choice of LGPL license versions is made available with the language indicating\n"
374 "that LGPLv2 or any later version may be used, or where a choice of which version\n"
375 "of the LGPL is applied is otherwise unspecified.\n";
376
377/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
378static RTSTRTUPLE const g_aCopyrightCommentStart[] =
379{
380 { RT_STR_TUPLE("<invalid> ") },
381 { RT_STR_TUPLE("/*") },
382 { RT_STR_TUPLE("#") },
383 { RT_STR_TUPLE("\"\"\"") },
384 { RT_STR_TUPLE(";") },
385 { RT_STR_TUPLE("REM") },
386 { RT_STR_TUPLE("rem") },
387 { RT_STR_TUPLE("Rem") },
388 { RT_STR_TUPLE("--") },
389 { RT_STR_TUPLE("'") },
390 { RT_STR_TUPLE("<end>") },
391};
392
393/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
394static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
395{
396 { RT_STR_TUPLE("<invalid> ") },
397 { RT_STR_TUPLE(" * ") },
398 { RT_STR_TUPLE("# ") },
399 { RT_STR_TUPLE("") },
400 { RT_STR_TUPLE("; ") },
401 { RT_STR_TUPLE("REM ") },
402 { RT_STR_TUPLE("rem ") },
403 { RT_STR_TUPLE("Rem ") },
404 { RT_STR_TUPLE("-- ") },
405 { RT_STR_TUPLE("' ") },
406 { RT_STR_TUPLE("<end>") },
407};
408
409/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
410static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
411{
412 { RT_STR_TUPLE("<invalid>") },
413 { RT_STR_TUPLE(" *") },
414 { RT_STR_TUPLE("#") },
415 { RT_STR_TUPLE("") },
416 { RT_STR_TUPLE(";") },
417 { RT_STR_TUPLE("REM") },
418 { RT_STR_TUPLE("rem") },
419 { RT_STR_TUPLE("Rem") },
420 { RT_STR_TUPLE("--") },
421 { RT_STR_TUPLE("'") },
422 { RT_STR_TUPLE("<end>") },
423};
424
425/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
426static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
427{
428 { RT_STR_TUPLE("<invalid> ") },
429 { RT_STR_TUPLE(" */") },
430 { RT_STR_TUPLE("#") },
431 { RT_STR_TUPLE("\"\"\"") },
432 { RT_STR_TUPLE(";") },
433 { RT_STR_TUPLE("REM") },
434 { RT_STR_TUPLE("rem") },
435 { RT_STR_TUPLE("Rem") },
436 { RT_STR_TUPLE("--") },
437 { RT_STR_TUPLE("'") },
438 { RT_STR_TUPLE("<end>") },
439};
440
441
442/**
443 * Figures out the predominant casing of the "REM" keyword in a batch file.
444 *
445 * @returns Predominant comment style.
446 * @param pIn The file to scan. Will be rewound.
447 */
448static SCMCOMMENTSTYLE determinBatchFileCommentStyle(PSCMSTREAM pIn)
449{
450 /*
451 * Figure out whether it's using upper or lower case REM comments before
452 * doing the work.
453 */
454 uint32_t cUpper = 0;
455 uint32_t cLower = 0;
456 uint32_t cCamel = 0;
457 SCMEOL enmEol;
458 size_t cchLine;
459 const char *pchLine;
460 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
461 {
462 while ( cchLine > 2
463 && RT_C_IS_SPACE(*pchLine))
464 {
465 pchLine++;
466 cchLine--;
467 }
468 if ( ( cchLine > 3
469 && RT_C_IS_SPACE(pchLine[2]))
470 || cchLine == 3)
471 {
472 if ( pchLine[0] == 'R'
473 && pchLine[1] == 'E'
474 && pchLine[2] == 'M')
475 cUpper++;
476 else if ( pchLine[0] == 'r'
477 && pchLine[1] == 'e'
478 && pchLine[2] == 'm')
479 cLower++;
480 else if ( pchLine[0] == 'R'
481 && pchLine[1] == 'e'
482 && pchLine[2] == 'm')
483 cCamel++;
484 }
485 }
486
487 ScmStreamRewindForReading(pIn);
488
489 if (cLower >= cUpper && cLower >= cCamel)
490 return kScmCommentStyle_Rem_Lower;
491 if (cCamel >= cLower && cCamel >= cUpper)
492 return kScmCommentStyle_Rem_Camel;
493 return kScmCommentStyle_Rem_Upper;
494}
495
496
497/**
498 * Worker for isBlankLine.
499 *
500 * @returns true if blank, false if not.
501 * @param pchLine Pointer to the start of the line.
502 * @param cchLine The (encoded) length of the line, excluding EOL char.
503 */
504static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
505{
506 /*
507 * From the end, more likely to hit a non-blank char there.
508 */
509 while (cchLine-- > 0)
510 if (!RT_C_IS_BLANK(pchLine[cchLine]))
511 return false;
512 return true;
513}
514
515/**
516 * Helper for checking whether a line is blank.
517 *
518 * @returns true if blank, false if not.
519 * @param pchLine Pointer to the start of the line.
520 * @param cchLine The (encoded) length of the line, excluding EOL char.
521 */
522DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
523{
524 if (cchLine == 0)
525 return true;
526 /*
527 * We're more likely to fine a non-space char at the end of the line than
528 * at the start, due to source code indentation.
529 */
530 if (pchLine[cchLine - 1])
531 return false;
532
533 /*
534 * Don't bother inlining loop code.
535 */
536 return isBlankLineSlow(pchLine, cchLine);
537}
538
539
540/**
541 * Strip trailing blanks (space & tab).
542 *
543 * @returns True if modified, false if not.
544 * @param pIn The input stream.
545 * @param pOut The output stream.
546 * @param pSettings The settings.
547 */
548bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
549{
550 if (!pSettings->fStripTrailingBlanks)
551 return false;
552
553 bool fModified = false;
554 SCMEOL enmEol;
555 size_t cchLine;
556 const char *pchLine;
557 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
558 {
559 int rc;
560 if ( cchLine == 0
561 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
562 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
563 else
564 {
565 cchLine--;
566 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
567 cchLine--;
568 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
569 fModified = true;
570 }
571 if (RT_FAILURE(rc))
572 return false;
573 }
574 if (fModified)
575 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
576 return fModified;
577}
578
579/**
580 * Expand tabs.
581 *
582 * @returns True if modified, false if not.
583 * @param pIn The input stream.
584 * @param pOut The output stream.
585 * @param pSettings The settings.
586 */
587bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
588{
589 if (!pSettings->fConvertTabs)
590 return false;
591
592 size_t const cchTab = pSettings->cchTab;
593 bool fModified = false;
594 SCMEOL enmEol;
595 size_t cchLine;
596 const char *pchLine;
597 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
598 {
599 int rc;
600 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
601 if (!pchTab)
602 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
603 else
604 {
605 size_t offTab = 0;
606 const char *pchChunk = pchLine;
607 for (;;)
608 {
609 size_t cchChunk = pchTab - pchChunk;
610 offTab += cchChunk;
611 ScmStreamWrite(pOut, pchChunk, cchChunk);
612
613 size_t cchToTab = cchTab - offTab % cchTab;
614 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
615 offTab += cchToTab;
616
617 pchChunk = pchTab + 1;
618 size_t cchLeft = cchLine - (pchChunk - pchLine);
619 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
620 if (!pchTab)
621 {
622 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
623 break;
624 }
625 }
626
627 fModified = true;
628 }
629 if (RT_FAILURE(rc))
630 return false;
631 }
632 if (fModified)
633 ScmVerbose(pState, 2, " * Expanded tabs\n");
634 return fModified;
635}
636
637/**
638 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
639 *
640 * @returns true if modifications were made, false if not.
641 * @param pIn The input stream.
642 * @param pOut The output stream.
643 * @param pSettings The settings.
644 * @param enmDesiredEol The desired end of line indicator type.
645 * @param pszDesiredSvnEol The desired svn:eol-style.
646 */
647static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
648 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
649{
650 if (!pSettings->fConvertEol)
651 return false;
652
653 bool fModified = false;
654 SCMEOL enmEol;
655 size_t cchLine;
656 const char *pchLine;
657 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
658 {
659 if ( enmEol != enmDesiredEol
660 && enmEol != SCMEOL_NONE)
661 {
662 fModified = true;
663 enmEol = enmDesiredEol;
664 }
665 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
666 if (RT_FAILURE(rc))
667 return false;
668 }
669 if (fModified)
670 ScmVerbose(pState, 2, " * Converted EOL markers\n");
671
672 /* Check svn:eol-style if appropriate */
673 if ( pSettings->fSetSvnEol
674 && ScmSvnIsInWorkingCopy(pState))
675 {
676 char *pszEol;
677 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
678 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
679 || rc == VERR_NOT_FOUND)
680 {
681 if (rc == VERR_NOT_FOUND)
682 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
683 else
684 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
685 int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
686 if (RT_FAILURE(rc2))
687 ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
688 }
689 if (RT_SUCCESS(rc))
690 RTStrFree(pszEol);
691 }
692
693 /** @todo also check the subversion svn:eol-style state! */
694 return fModified;
695}
696
697/**
698 * Force native end of line indicator.
699 *
700 * @returns true if modifications were made, false if not.
701 * @param pIn The input stream.
702 * @param pOut The output stream.
703 * @param pSettings The settings.
704 */
705bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
706{
707#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
708 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
709#else
710 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
711#endif
712}
713
714/**
715 * Force the stream to use LF as the end of line indicator.
716 *
717 * @returns true if modifications were made, false if not.
718 * @param pIn The input stream.
719 * @param pOut The output stream.
720 * @param pSettings The settings.
721 */
722bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
723{
724 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
725}
726
727/**
728 * Force the stream to use CRLF as the end of line indicator.
729 *
730 * @returns true if modifications were made, false if not.
731 * @param pIn The input stream.
732 * @param pOut The output stream.
733 * @param pSettings The settings.
734 */
735bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
736{
737 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
738}
739
740/**
741 * Strip trailing blank lines and/or make sure there is exactly one blank line
742 * at the end of the file.
743 *
744 * @returns true if modifications were made, false if not.
745 * @param pIn The input stream.
746 * @param pOut The output stream.
747 * @param pSettings The settings.
748 *
749 * @remarks ASSUMES trailing white space has been removed already.
750 */
751bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
752{
753 if ( !pSettings->fStripTrailingLines
754 && !pSettings->fForceTrailingLine
755 && !pSettings->fForceFinalEol)
756 return false;
757
758 size_t const cLines = ScmStreamCountLines(pIn);
759
760 /* Empty files remains empty. */
761 if (cLines <= 1)
762 return false;
763
764 /* Figure out if we need to adjust the number of lines or not. */
765 size_t cLinesNew = cLines;
766
767 if ( pSettings->fStripTrailingLines
768 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
769 {
770 while ( cLinesNew > 1
771 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
772 cLinesNew--;
773 }
774
775 if ( pSettings->fForceTrailingLine
776 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
777 cLinesNew++;
778
779 bool fFixMissingEol = pSettings->fForceFinalEol
780 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
781
782 if ( !fFixMissingEol
783 && cLines == cLinesNew)
784 return false;
785
786 /* Copy the number of lines we've arrived at. */
787 ScmStreamRewindForReading(pIn);
788
789 size_t cCopied = RT_MIN(cLinesNew, cLines);
790 ScmStreamCopyLines(pOut, pIn, cCopied);
791
792 if (cCopied != cLinesNew)
793 {
794 while (cCopied++ < cLinesNew)
795 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
796 }
797 /* Fix missing EOL if required. */
798 else if (fFixMissingEol)
799 {
800 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
801 ScmStreamWrite(pOut, "\n", 1);
802 else
803 ScmStreamWrite(pOut, "\r\n", 2);
804 }
805
806 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
807 return true;
808}
809
810/**
811 * Make sure there is no svn:executable property on the current file.
812 *
813 * @returns false - the state carries these kinds of changes.
814 * @param pState The rewriter state.
815 * @param pIn The input stream.
816 * @param pOut The output stream.
817 * @param pSettings The settings.
818 */
819bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
820{
821 RT_NOREF2(pIn, pOut);
822 if ( !pSettings->fSetSvnExecutable
823 || !ScmSvnIsInWorkingCopy(pState))
824 return false;
825
826 int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
827 if (RT_SUCCESS(rc))
828 {
829 ScmVerbose(pState, 2, " * removing svn:executable\n");
830 rc = ScmSvnDelProperty(pState, "svn:executable");
831 if (RT_FAILURE(rc))
832 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
833 }
834 return false;
835}
836
837/**
838 * Make sure there is no svn:keywords property on the current file.
839 *
840 * @returns false - the state carries these kinds of changes.
841 * @param pState The rewriter state.
842 * @param pIn The input stream.
843 * @param pOut The output stream.
844 * @param pSettings The settings.
845 */
846bool rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
847{
848 RT_NOREF2(pIn, pOut);
849 if ( !pSettings->fSetSvnExecutable
850 || !ScmSvnIsInWorkingCopy(pState))
851 return false;
852
853 int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
854 if (RT_SUCCESS(rc))
855 {
856 ScmVerbose(pState, 2, " * removing svn:keywords\n");
857 rc = ScmSvnDelProperty(pState, "svn:keywords");
858 if (RT_FAILURE(rc))
859 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
860 }
861 return false;
862}
863
864/**
865 * Make sure there is no svn:eol-style property on the current file.
866 *
867 * @returns false - the state carries these kinds of changes.
868 * @param pState The rewriter state.
869 * @param pIn The input stream.
870 * @param pOut The output stream.
871 * @param pSettings The settings.
872 */
873bool rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
874{
875 RT_NOREF2(pIn, pOut);
876 if ( !pSettings->fSetSvnExecutable
877 || !ScmSvnIsInWorkingCopy(pState))
878 return false;
879
880 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
881 if (RT_SUCCESS(rc))
882 {
883 ScmVerbose(pState, 2, " * removing svn:eol-style\n");
884 rc = ScmSvnDelProperty(pState, "svn:eol-style");
885 if (RT_FAILURE(rc))
886 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
887 }
888 return false;
889}
890
891/**
892 * Makes sure the svn properties are appropriate for a binary.
893 *
894 * @returns false - the state carries these kinds of changes.
895 * @param pState The rewriter state.
896 * @param pIn The input stream.
897 * @param pOut The output stream.
898 * @param pSettings The settings.
899 */
900bool rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
901{
902 RT_NOREF2(pIn, pOut);
903 if ( !pSettings->fSetSvnExecutable
904 || !ScmSvnIsInWorkingCopy(pState))
905 return false;
906
907 /* remove svn:eol-style and svn:keywords */
908 static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
909 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
910 {
911 char *pszValue;
912 int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
913 if (RT_SUCCESS(rc))
914 {
915 ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
916 RTStrFree(pszValue);
917 rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
918 if (RT_FAILURE(rc))
919 ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
920 }
921 else if (rc != VERR_NOT_FOUND)
922 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
923 }
924
925 /* Make sure there is a svn:mime-type set. */
926 int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
927 if (rc == VERR_NOT_FOUND)
928 {
929 ScmVerbose(pState, 2, " * settings svn:mime-type\n");
930 rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
931 if (RT_FAILURE(rc))
932 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
933 }
934 else if (RT_FAILURE(rc))
935 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
936
937 return false;
938}
939
940/**
941 * Make sure the Id and Revision keywords are expanded.
942 *
943 * @returns false - the state carries these kinds of changes.
944 * @param pState The rewriter state.
945 * @param pIn The input stream.
946 * @param pOut The output stream.
947 * @param pSettings The settings.
948 */
949bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
950{
951 RT_NOREF2(pIn, pOut);
952 if ( !pSettings->fSetSvnKeywords
953 || !ScmSvnIsInWorkingCopy(pState))
954 return false;
955
956 char *pszKeywords;
957 int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
958 if ( RT_SUCCESS(rc)
959 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
960 || !strstr(pszKeywords, "Revision")) )
961 {
962 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
963 rc = RTStrAAppend(&pszKeywords, " Id Revision");
964 else if (!strstr(pszKeywords, "Id"))
965 rc = RTStrAAppend(&pszKeywords, " Id");
966 else
967 rc = RTStrAAppend(&pszKeywords, " Revision");
968 if (RT_SUCCESS(rc))
969 {
970 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
971 rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
972 if (RT_FAILURE(rc))
973 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
974 }
975 else
976 ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
977 RTStrFree(pszKeywords);
978 }
979 else if (rc == VERR_NOT_FOUND)
980 {
981 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
982 rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
983 if (RT_FAILURE(rc))
984 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
985 }
986 else if (RT_SUCCESS(rc))
987 RTStrFree(pszKeywords);
988
989 return false;
990}
991
992/**
993 * Checks the svn:sync-process value and that parent is exported too.
994 *
995 * @returns false - the state carries these kinds of changes.
996 * @param pState The rewriter state.
997 * @param pIn The input stream.
998 * @param pOut The output stream.
999 * @param pSettings The settings.
1000 */
1001bool rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1002{
1003 RT_NOREF2(pIn, pOut);
1004 if ( pSettings->fSkipSvnSyncProcess
1005 || !ScmSvnIsInWorkingCopy(pState))
1006 return false;
1007
1008 char *pszSyncProcess;
1009 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1010 if (RT_SUCCESS(rc))
1011 {
1012 if (strcmp(pszSyncProcess, "export") == 0)
1013 {
1014 char *pszParentSyncProcess;
1015 rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
1016 if (RT_SUCCESS(rc))
1017 {
1018 if (strcmp(pszSyncProcess, "export") != 0)
1019 ScmError(pState, VERR_INVALID_STATE,
1020 "svn:sync-process=export, but parent directory differs: %s\n"
1021 "WARNING! Make sure to unexport everything inside the directory first!\n"
1022 " Then you may export the directory and stuff inside it if you want.\n"
1023 " (Just exporting the directory will not make anything inside it externally visible.)\n"
1024 , pszParentSyncProcess);
1025 RTStrFree(pszParentSyncProcess);
1026 }
1027 else if (rc == VERR_NOT_FOUND)
1028 ScmError(pState, VERR_NOT_FOUND,
1029 "svn:sync-process=export, but parent directory is not exported!\n"
1030 "WARNING! Make sure to unexport everything inside the directory first!\n"
1031 " Then you may export the directory and stuff inside it if you want.\n"
1032 " (Just exporting the directory will not make anything inside it externally visible.)\n");
1033 else
1034 ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
1035 }
1036 else if (strcmp(pszSyncProcess, "ignore") != 0)
1037 ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
1038 RTStrFree(pszSyncProcess);
1039 }
1040 else if (rc != VERR_NOT_FOUND)
1041 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1042
1043 return false;
1044}
1045
1046/**
1047 * Compares two strings word-by-word, ignoring spaces, punctuation and case.
1048 *
1049 * Assumes ASCII strings.
1050 *
1051 * @returns true if they match, false if not.
1052 * @param psz1 The first string. This is typically the known one.
1053 * @param psz2 The second string. This is typically the unknown one,
1054 * which is why we return a next pointer for this one.
1055 * @param ppsz2Next Where to return the next part of the 2nd string. If
1056 * this is NULL, the whole string must match.
1057 */
1058static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
1059{
1060 for (;;)
1061 {
1062 /* Try compare raw strings first. */
1063 char ch1 = *psz1;
1064 char ch2 = *psz2;
1065 if ( ch1 == ch2
1066 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
1067 {
1068 if (ch1)
1069 {
1070 psz1++;
1071 psz2++;
1072 }
1073 else
1074 {
1075 if (ppsz2Next)
1076 *ppsz2Next = psz2;
1077 return true;
1078 }
1079 }
1080 else
1081 {
1082 /* Try skip spaces an punctuation. */
1083 while ( RT_C_IS_SPACE(ch1)
1084 || RT_C_IS_PUNCT(ch1))
1085 ch1 = *++psz1;
1086
1087 if (ch1 == '\0' && ppsz2Next)
1088 {
1089 *ppsz2Next = psz2;
1090 return true;
1091 }
1092
1093 while ( RT_C_IS_SPACE(ch2)
1094 || RT_C_IS_PUNCT(ch2))
1095 ch2 = *++psz2;
1096
1097 if ( ch1 != ch2
1098 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
1099 {
1100 if (ppsz2Next)
1101 *ppsz2Next = psz2;
1102 return false;
1103 }
1104 }
1105 }
1106}
1107
1108/**
1109 * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
1110 * and case.
1111 *
1112 * @returns true if found, false if not.
1113 * @param pszText The haystack to search in.
1114 * @param cchText The length @a pszText.
1115 * @param pszFragment The needle to search for.
1116 * @param ppszStart Where to return the address in @a pszText where
1117 * the fragment was found. Optional.
1118 * @param ppszNext Where to return the pointer to the first char in
1119 * @a pszText after the fragment. Optional.
1120 *
1121 * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
1122 * This character must not be space or punctuation.
1123 */
1124static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
1125 const char **ppszStart, const char **ppszNext)
1126{
1127 Assert(!((unsigned)*pszFragment & 0x80));
1128 Assert(pszText[cchText] == '\0');
1129 Assert(!RT_C_IS_BLANK(*pszFragment));
1130 Assert(!RT_C_IS_PUNCT(*pszFragment));
1131
1132 char chLower = RT_C_TO_LOWER(*pszFragment);
1133 char chUpper = RT_C_TO_UPPER(*pszFragment);
1134 for (;;)
1135 {
1136 const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
1137 const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
1138 if (!pszHit && !pszHit2)
1139 {
1140 if (ppszStart)
1141 *ppszStart = NULL;
1142 if (ppszNext)
1143 *ppszNext = NULL;
1144 return false;
1145 }
1146
1147 if ( pszHit == NULL
1148 || ( pszHit2 != NULL
1149 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
1150 pszHit = pszHit2;
1151
1152 const char *pszNext;
1153 if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
1154 {
1155 if (ppszStart)
1156 *ppszStart = pszHit;
1157 if (ppszNext)
1158 *ppszNext = pszNext;
1159 return true;
1160 }
1161
1162 cchText -= pszHit - pszText + 1;
1163 pszText = pszHit + 1;
1164 }
1165}
1166
1167
1168/**
1169 * Counts the number of lines in the given substring.
1170 *
1171 * @returns The number of lines.
1172 * @param psz The start of the substring.
1173 * @param cch The length of the substring.
1174 */
1175static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
1176{
1177 uint32_t cLines = 0;
1178 for (;;)
1179 {
1180 const char *pszEol = (const char *)memchr(psz, '\n', cch);
1181 if (pszEol)
1182 cLines++;
1183 else
1184 return cLines + (*psz != '\0');
1185 cch -= pszEol + 1 - psz;
1186 if (!cch)
1187 return cLines;
1188 psz = pszEol + 1;
1189 }
1190}
1191
1192
1193/**
1194 * Comment parser callback for locating copyright and license.
1195 */
1196static DECLCALLBACK(int)
1197rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
1198{
1199 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
1200 Assert(strlen(pszBody) == cchBody);
1201 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
1202 ScmVerbose(pState->pState, 5,
1203 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
1204 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
1205 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
1206
1207 pState->cComments++;
1208
1209 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
1210
1211 /*
1212 * Look for a 'contributed by' or 'includes contributions from' line, these
1213 * comes first when present.
1214 */
1215 const char *pchContributedBy = NULL;
1216 size_t cchContributedBy = 0;
1217 size_t cBlankLinesAfterContributedBy = 0;
1218 if ( pState->pszContributedBy == NULL
1219 && ( pState->iLineCopyright == UINT32_MAX
1220 || pState->iLineLicense == UINT32_MAX)
1221 && ( ( cchBody > sizeof("Contributed by")
1222 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
1223 || ( cchBody > sizeof("Includes contributions from")
1224 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
1225 {
1226 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1227 while (pszNextLine && pszNextLine[1] != '\n')
1228 pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
1229 if (pszNextLine)
1230 {
1231 pchContributedBy = pszBody;
1232 cchContributedBy = pszNextLine - pszBody;
1233
1234 /* Skip the copyright line and any blank lines following it. */
1235 cchBody -= cchContributedBy + 1;
1236 pszBody = pszNextLine + 1;
1237 iLine += 1;
1238 while (*pszBody == '\n')
1239 {
1240 pszBody++;
1241 cchBody--;
1242 iLine++;
1243 cBlankLinesAfterContributedBy++;
1244 }
1245 }
1246 }
1247
1248 /*
1249 * Look for the copyright line.
1250 */
1251 bool fFoundCopyright = false;
1252 uint32_t cBlankLinesAfterCopyright = 0;
1253 if ( pState->iLineCopyright == UINT32_MAX
1254 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
1255 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
1256 {
1257 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1258
1259 /* Oracle copyright? */
1260 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
1261 while (RT_C_IS_SPACE(pszEnd[-1]))
1262 pszEnd--;
1263 if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
1264 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1265 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
1266 {
1267 /* Parse out the year(s). */
1268 const char *psz = pszBody + sizeof("copyright");
1269 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
1270 psz++;
1271 if (RT_C_IS_DIGIT(*psz))
1272 {
1273 char *pszNext;
1274 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
1275 if ( RT_SUCCESS(rc)
1276 && rc != VWRN_NUMBER_TOO_BIG
1277 && rc != VWRN_NEGATIVE_UNSIGNED)
1278 {
1279 if ( pState->uFirstYear < 1975
1280 || pState->uFirstYear > 3000)
1281 {
1282 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
1283 pState->uFirstYear, pszEnd - pszBody, pszBody);
1284 pState->uFirstYear = UINT32_MAX;
1285 }
1286
1287 while (RT_C_IS_SPACE(*pszNext))
1288 pszNext++;
1289 if (*pszNext == '-')
1290 {
1291 do
1292 pszNext++;
1293 while (RT_C_IS_SPACE(*pszNext));
1294 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
1295 if ( RT_SUCCESS(rc)
1296 && rc != VWRN_NUMBER_TOO_BIG
1297 && rc != VWRN_NEGATIVE_UNSIGNED)
1298 {
1299 if ( pState->uLastYear < 1975
1300 || pState->uLastYear > 3000)
1301 {
1302 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
1303 pState->uLastYear, pszEnd - pszBody, pszBody);
1304 pState->uLastYear = UINT32_MAX;
1305 }
1306 else if (pState->uFirstYear > pState->uLastYear)
1307 {
1308 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
1309 uint32_t iTmp = pState->uLastYear;
1310 pState->uLastYear = pState->uFirstYear;
1311 pState->uFirstYear = iTmp;
1312 }
1313 }
1314 else
1315 {
1316 pState->uLastYear = UINT32_MAX;
1317 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1318 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1319 }
1320 }
1321 else if (*pszNext != g_szCopyrightHolder[0])
1322 ScmError(pState->pState, VERR_PARSE_ERROR,
1323 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1324 else
1325 pState->uLastYear = pState->uFirstYear;
1326 }
1327 else
1328 {
1329 pState->uFirstYear = UINT32_MAX;
1330 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1331 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1332 }
1333 }
1334
1335 /* The copyright comment must come before the license. */
1336 if (pState->iLineLicense != UINT32_MAX)
1337 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1338 iLine, pState->iLineLicense);
1339
1340 /* In C/C++ code, this must be a multiline comment. While in python it
1341 must be a */
1342 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1343 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1344 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1345 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1346
1347 /* The copyright must be followed by the license. */
1348 if (!pszNextLine)
1349 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1350
1351 /* Quit if we've flagged a failure. */
1352 if (RT_FAILURE(pState->pState->rc))
1353 return VERR_CALLBACK_RETURN;
1354
1355 /* Check if it's well formed and up to date. */
1356 char szWellFormed[256];
1357 size_t cchWellFormed;
1358 if (pState->uFirstYear == pState->uLastYear)
1359 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1360 pState->uFirstYear, g_szCopyrightHolder);
1361 else
1362 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1363 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1364 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1365 pState->iLineCopyright = iLine;
1366 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1367 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1368 if (!pState->fWellFormedCopyright)
1369 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1370
1371 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1372 if (pInfo->cBlankLinesBefore != 1)
1373 {
1374 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1375 pInfo->cBlankLinesBefore);
1376 pState->fWellFormedCopyright = false;
1377 }
1378
1379 /* If the comment doesn't start in column 1, trigger rewrite. */
1380 if (pInfo->offStart != 0)
1381 {
1382 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1383 pState->fWellFormedCopyright = false;
1384 /** @todo check that there isn't any code preceeding the comment. */
1385 }
1386
1387 if (pchContributedBy)
1388 {
1389 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1390 if (cBlankLinesAfterContributedBy != 1)
1391 {
1392 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1393 cBlankLinesAfterContributedBy);
1394 pState->fWellFormedCopyright = false;
1395 }
1396 }
1397
1398 fFoundCopyright = true;
1399 ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1400 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1401 }
1402 else
1403 ScmVerbose(pState->pState, 3, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1404
1405 if (!pszNextLine)
1406 return VINF_SUCCESS;
1407
1408 /* Skip the copyright line and any blank lines following it. */
1409 cchBody -= pszNextLine - pszBody + 1;
1410 pszBody = pszNextLine + 1;
1411 iLine += 1;
1412 while (*pszBody == '\n')
1413 {
1414 pszBody++;
1415 cchBody--;
1416 iLine++;
1417 cBlankLinesAfterCopyright++;
1418 }
1419
1420 /*
1421 * If we have a based-on-mit scenario, check for the lead in now and
1422 * complain if not found.
1423 */
1424 if ( fFoundCopyright
1425 && pState->enmLicenceOpt == kScmLicense_BasedOnMit
1426 && pState->iLineLicense == UINT32_MAX)
1427 {
1428 if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
1429 {
1430 /* Take down a comment area which goes up to 'this file is based on'.
1431 The license line and length isn't used but gets set to cover the current line. */
1432 pState->iLineComment = pInfo->iLineStart;
1433 pState->cLinesComment = iLine - pInfo->iLineStart;
1434 pState->iLineLicense = iLine;
1435 pState->cLinesLicense = 1;
1436 pState->fExternalLicense = true;
1437 pState->fIsCorrectLicense = true;
1438 pState->fWellFormedLicense = true;
1439
1440 /* Check if we've got a MIT a license here or not. */
1441 pState->pCurrentLicense = NULL;
1442 do
1443 {
1444 const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
1445 if (!pszEol || pszEol[1] == '\0')
1446 {
1447 pszBody += cchBody;
1448 cchBody = 0;
1449 break;
1450 }
1451 cchBody -= pszEol - pszBody + 1;
1452 pszBody = pszEol + 1;
1453 iLine++;
1454
1455 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1456 {
1457 const char *pszNext;
1458 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1459 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1460 {
1461 pState->pCurrentLicense = pCur;
1462 break;
1463 }
1464 }
1465 } while (!pState->pCurrentLicense);
1466 if (!pState->pCurrentLicense)
1467 ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
1468 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
1469 ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
1470 pState->pCurrentLicense->psz);
1471 }
1472 else
1473 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
1474 return VINF_SUCCESS;
1475 }
1476 }
1477
1478 /*
1479 * Look for LGPL like text in the comment.
1480 */
1481 if (pState->fCheckforLgpl && cchBody > 128)
1482 {
1483 /* We look for typical LGPL notices. */
1484 if (pState->iLineLgplNotice == UINT32_MAX)
1485 {
1486 static const char * const s_apszFragments[] =
1487 {
1488 "under the terms of the GNU Lesser General Public License",
1489 };
1490 for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
1491 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
1492 {
1493 pState->iLineLgplNotice = iLine;
1494 pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
1495 ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
1496 break;
1497 }
1498 }
1499
1500 if ( pState->iLineLgplDisclaimer == UINT32_MAX
1501 && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
1502 {
1503 pState->iLineLgplDisclaimer = iLine;
1504 ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
1505 }
1506 }
1507
1508 /*
1509 * Look for the license text.
1510 */
1511 if (pState->iLineLicense == UINT32_MAX)
1512 {
1513 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1514 {
1515 const char *pszNext;
1516 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1517 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1518 {
1519 while ( RT_C_IS_SPACE(*pszNext)
1520 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1521 pszNext++;
1522
1523 uint32_t cDashes = 0;
1524 while (*pszNext == '-')
1525 cDashes++, pszNext++;
1526 bool fExternal = cDashes > 10;
1527
1528 if ( *pszNext == '\0'
1529 || fExternal)
1530 {
1531 /* In C/C++ code, this must be a multiline comment. While in python it
1532 must be a */
1533 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1534 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1535 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1536 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1537
1538 /* Quit if we've flagged a failure. */
1539 if (RT_FAILURE(pState->pState->rc))
1540 return VERR_CALLBACK_RETURN;
1541
1542 /* Record it. */
1543 pState->iLineLicense = iLine;
1544 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
1545 pState->pCurrentLicense = pCur;
1546 pState->fExternalLicense = fExternal;
1547 pState->fIsCorrectLicense = pState->fOpenSource
1548 ? pCur == pState->pExpectedLicense
1549 : pCur->enmType == kScmLicenseType_Confidential;
1550 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1551 if (!pState->fWellFormedLicense)
1552 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1553
1554 /* If there was more than one blank line between the copyright and the
1555 license text, extend the license text area and force a rewrite of it. */
1556 if (cBlankLinesAfterCopyright > 1)
1557 {
1558 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1559 cBlankLinesAfterCopyright);
1560 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1561 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1562 pState->fWellFormedLicense = false;
1563 }
1564
1565 /* If there was more than one blank line after the license, trigger a rewrite. */
1566 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1567 {
1568 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1569 pInfo->cBlankLinesAfter);
1570 pState->fWellFormedLicense = false;
1571 }
1572
1573 /** @todo Check that the last comment line doesn't have any code on it. */
1574 /** @todo Check that column 2 contains '*' for C/C++ files. */
1575
1576 ScmVerbose(pState->pState, 3,
1577 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1578 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1579 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1580 pState->fExternalLicense, pState->fOpenSource);
1581
1582 if (fFoundCopyright)
1583 {
1584 pState->iLineComment = pInfo->iLineStart;
1585 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1586 - pInfo->iLineStart;
1587 }
1588 else
1589 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1590 break;
1591 }
1592 }
1593 }
1594 }
1595
1596 if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
1597 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1598
1599 /*
1600 * Stop looking for stuff after 100 comments.
1601 */
1602 if (pState->cComments > 100)
1603 return VERR_CALLBACK_RETURN;
1604 return VINF_SUCCESS;
1605}
1606
1607/**
1608 * Writes comment body text.
1609 *
1610 * @returns Stream status.
1611 * @param pOut The output stream.
1612 * @param pszText The text to write.
1613 * @param cchText The length of the text.
1614 * @param enmCommentStyle The comment style.
1615 * @param enmEol The EOL style.
1616 */
1617static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
1618 SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
1619{
1620 Assert(pszText[cchText - 1] == '\n');
1621 Assert(pszText[cchText - 2] != '\n');
1622 NOREF(cchText);
1623 do
1624 {
1625 const char *pszEol = strchr(pszText, '\n');
1626 if (pszEol != pszText)
1627 {
1628 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1629 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1630 ScmStreamWrite(pOut, pszText, pszEol - pszText);
1631 ScmStreamPutEol(pOut, enmEol);
1632 }
1633 else
1634 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1635 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1636 pszText = pszEol + 1;
1637 } while (*pszText != '\0');
1638 return ScmStreamGetStatus(pOut);
1639}
1640
1641
1642/**
1643 * Updates the copyright year and/or license text.
1644 *
1645 * @returns true if modifications were made, false if not.
1646 * @param pState The rewriter state.
1647 * @param pIn The input stream.
1648 * @param pOut The output stream.
1649 * @param pSettings The settings.
1650 * @param enmCommentStyle The comment style used by the file.
1651 */
1652static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1653 SCMCOMMENTSTYLE enmCommentStyle)
1654{
1655 if ( !pSettings->fUpdateCopyrightYear
1656 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1657 return false;
1658
1659 /*
1660 * Try locate the relevant comments.
1661 */
1662 SCMCOPYRIGHTINFO Info =
1663 {
1664 /*.pState = */ pState,
1665 /*.enmCommentStyle = */ enmCommentStyle,
1666
1667 /*.cComments = */ 0,
1668
1669 /*.pszContributedBy = */ NULL,
1670
1671 /*.iLineComment = */ UINT32_MAX,
1672 /*.cLinesComment = */ 0,
1673
1674 /*.iLineCopyright = */ UINT32_MAX,
1675 /*.uFirstYear = */ UINT32_MAX,
1676 /*.uLastYear = */ UINT32_MAX,
1677 /*.fWellFormedCopyright = */ false,
1678 /*.fUpToDateCopyright = */ false,
1679
1680 /*.fOpenSource = */ true,
1681 /*.pExpectedLicense = */ NULL,
1682 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
1683 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
1684 ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1685 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1686 /*.iLineLicense = */ UINT32_MAX,
1687 /*.cLinesLicense = */ 0,
1688 /*.pCurrentLicense = */ NULL,
1689 /*.fIsCorrectLicense = */ false,
1690 /*.fWellFormedLicense = */ false,
1691 /*.fExternalLicense = */ false,
1692
1693 /*.fCheckForLgpl = */ true,
1694 /*.iLineLgplNotice = */ UINT32_MAX,
1695 /*.iLineAfterLgplComment = */ UINT32_MAX,
1696 /*.iLineLgplDisclaimer = */ UINT32_MAX,
1697 };
1698
1699 /* Figure Info.fOpenSource and the desired license: */
1700 char *pszSyncProcess;
1701 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1702 if (RT_SUCCESS(rc))
1703 {
1704 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1705 RTStrFree(pszSyncProcess);
1706 }
1707 else if (rc == VERR_NOT_FOUND)
1708 Info.fOpenSource = false;
1709 else
1710 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1711
1712 Info.pExpectedLicense = Info.paLicenses;
1713 if (Info.fOpenSource)
1714 {
1715 if ( pSettings->enmUpdateLicense != kScmLicense_Mit
1716 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1717 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1718 Info.pExpectedLicense++;
1719 else
1720 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
1721 }
1722 else
1723 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1724 Info.pExpectedLicense++;
1725
1726 /* Scan the comments. */
1727 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1728 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1729 && RT_SUCCESS(pState->rc))
1730 {
1731 /*
1732 * Do conformity checks.
1733 */
1734 bool fAddLgplDisclaimer = false;
1735 if (Info.fCheckforLgpl)
1736 {
1737 if ( Info.iLineLgplNotice != UINT32_MAX
1738 && Info.iLineLgplDisclaimer == UINT32_MAX)
1739 {
1740 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
1741 ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
1742 Info.iLineLgplNotice + 1);
1743 else
1744 {
1745 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
1746 fAddLgplDisclaimer = true;
1747 }
1748 }
1749 else if ( Info.iLineLgplNotice == UINT32_MAX
1750 && Info.iLineLgplDisclaimer != UINT32_MAX)
1751 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
1752 Info.iLineLgplDisclaimer + 1);
1753 }
1754
1755 if (!pSettings->fExternalCopyright)
1756 {
1757 if (Info.iLineCopyright == UINT32_MAX)
1758 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1759 if (Info.iLineLicense == UINT32_MAX)
1760 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1761 }
1762 else if (Info.iLineCopyright != UINT32_MAX)
1763 ScmError(pState, VERR_NOT_FOUND,
1764 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1765 Info.iLineCopyright + 1);
1766
1767
1768 if (RT_SUCCESS(pState->rc))
1769 {
1770 /*
1771 * Do we need to make any changes?
1772 */
1773 bool fUpdateCopyright = !pSettings->fExternalCopyright
1774 && ( !Info.fWellFormedCopyright
1775 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
1776 bool fUpdateLicense = !pSettings->fExternalCopyright
1777 && Info.enmLicenceOpt != kScmLicense_LeaveAlone
1778 && ( !Info.fWellFormedLicense
1779 || !Info.fIsCorrectLicense);
1780 if ( fUpdateCopyright
1781 || fUpdateLicense
1782 || fAddLgplDisclaimer)
1783 {
1784 Assert(Info.iLineComment != UINT32_MAX);
1785 Assert(Info.cLinesComment > 0);
1786
1787 /*
1788 * Okay, do the work.
1789 */
1790 ScmStreamRewindForReading(pIn);
1791
1792 if (pSettings->fUpdateCopyrightYear)
1793 Info.uLastYear = g_uYear;
1794
1795 uint32_t iLine = 0;
1796 SCMEOL enmEol;
1797 size_t cchLine;
1798 const char *pchLine;
1799 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1800 {
1801 if ( iLine == Info.iLineComment
1802 && (fUpdateCopyright || fUpdateLicense) )
1803 {
1804 /* Leading blank line. */
1805 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1806 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1807
1808 /* Contributed by someone? */
1809 if (Info.pszContributedBy)
1810 {
1811 const char *psz = Info.pszContributedBy;
1812 for (;;)
1813 {
1814 const char *pszEol = strchr(psz, '\n');
1815 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
1816 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1817 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1818 ScmStreamWrite(pOut, psz, cchContribLine);
1819 ScmStreamPutEol(pOut, enmEol);
1820 if (!pszEol)
1821 break;
1822 psz = pszEol + 1;
1823 }
1824
1825 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1826 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1827 }
1828
1829 /* Write the copyright comment line. */
1830 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1831 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1832
1833 char szCopyright[256];
1834 size_t cchCopyright;
1835 if (Info.uFirstYear == Info.uLastYear)
1836 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1837 Info.uFirstYear, g_szCopyrightHolder);
1838 else
1839 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1840 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1841
1842 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1843 ScmStreamPutEol(pOut, enmEol);
1844
1845 if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1846 {
1847 /* Blank line separating the two. */
1848 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1849 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1850
1851 /* Write the license text. */
1852 scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
1853 enmCommentStyle, enmEol);
1854
1855 /* Final comment line. */
1856 if (!Info.fExternalLicense)
1857 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1858 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1859 }
1860 else
1861 Assert(Info.fExternalLicense);
1862
1863 /* Skip the copyright and license text in the input file. */
1864 rc = ScmStreamGetStatus(pOut);
1865 if (RT_SUCCESS(rc))
1866 {
1867 iLine = Info.iLineComment + Info.cLinesComment;
1868 rc = ScmStreamSeekByLine(pIn, iLine);
1869 }
1870 }
1871 /*
1872 * Add LGPL disclaimer?
1873 */
1874 else if ( iLine == Info.iLineAfterLgplComment
1875 && fAddLgplDisclaimer)
1876 {
1877 ScmStreamPutEol(pOut, enmEol);
1878 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1879 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1880 scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
1881 enmCommentStyle, enmEol);
1882 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1883 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1884
1885 /* put the actual line */
1886 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1887 iLine++;
1888 }
1889 else
1890 {
1891 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1892 iLine++;
1893 }
1894 if (RT_FAILURE(rc))
1895 {
1896 RTStrFree(Info.pszContributedBy);
1897 return false;
1898 }
1899 } /* for each source line */
1900
1901 RTStrFree(Info.pszContributedBy);
1902 return true;
1903 }
1904 }
1905 }
1906 else
1907 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1908 NOREF(pState); NOREF(pOut);
1909 RTStrFree(Info.pszContributedBy);
1910 return false;
1911}
1912
1913
1914/** Copyright updater for C-style comments. */
1915bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1916{
1917 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
1918}
1919
1920/** Copyright updater for hash-prefixed comments. */
1921bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1922{
1923 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
1924}
1925
1926/** Copyright updater for REM-prefixed comments. */
1927bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1928{
1929 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
1930}
1931
1932/** Copyright updater for python comments. */
1933bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1934{
1935 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
1936}
1937
1938/** Copyright updater for semicolon-prefixed comments. */
1939bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1940{
1941 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
1942}
1943
1944/** Copyright updater for sql comments. */
1945bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1946{
1947 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
1948}
1949
1950/** Copyright updater for tick-prefixed comments. */
1951bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1952{
1953 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
1954}
1955
1956
1957/**
1958 * Makefile.kup are empty files, enforce this.
1959 *
1960 * @returns true if modifications were made, false if not.
1961 * @param pIn The input stream.
1962 * @param pOut The output stream.
1963 * @param pSettings The settings.
1964 */
1965bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1966{
1967 RT_NOREF2(pOut, pSettings);
1968
1969 /* These files should be zero bytes. */
1970 if (pIn->cb == 0)
1971 return false;
1972 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
1973 return true;
1974}
1975
1976/**
1977 * Rewrite a kBuild makefile.
1978 *
1979 * @returns true if modifications were made, false if not.
1980 * @param pIn The input stream.
1981 * @param pOut The output stream.
1982 * @param pSettings The settings.
1983 *
1984 * @todo
1985 *
1986 * Ideas for Makefile.kmk and Config.kmk:
1987 * - sort if1of/ifn1of sets.
1988 * - line continuation slashes should only be preceded by one space.
1989 */
1990bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1991{
1992 RT_NOREF4(pState, pIn, pOut, pSettings);
1993 return false;
1994}
1995
1996
1997static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
1998 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
1999{
2000 *ppchText = NULL;
2001 *pcchText = 0;
2002 *pfNeedFixing = false;
2003
2004 /*
2005 * The first line.
2006 */
2007 if (pchLine[0] != '/')
2008 return false;
2009 size_t offLine = 1;
2010 while (offLine < cchLine && pchLine[offLine] == '*')
2011 offLine++;
2012 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
2013 return false;
2014 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2015 offLine++;
2016 if (offLine != cchLine)
2017 return false;
2018
2019 size_t const cchBox = cchLine;
2020 *pfNeedFixing = cchBox != cchWidth;
2021
2022 /*
2023 * The next line, extracting the text.
2024 */
2025 SCMEOL enmEol;
2026 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2027 if (cchLine < cchBox - 3)
2028 return false;
2029
2030 offLine = 0;
2031 if (RT_C_IS_BLANK(pchLine[0]))
2032 {
2033 *pfNeedFixing = true;
2034 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2035 }
2036
2037 if (pchLine[offLine] != '*')
2038 return false;
2039 offLine++;
2040
2041 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
2042 return false;
2043 offLine++;
2044
2045 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2046 offLine++;
2047 if (offLine >= cchLine)
2048 return false;
2049 if (!RT_C_IS_UPPER(pchLine[offLine]))
2050 return false;
2051
2052 if (offLine != 4 || cchLine != cchBox)
2053 *pfNeedFixing = true;
2054
2055 *ppchText = &pchLine[offLine];
2056 size_t const offText = offLine;
2057
2058 /* From the end now. */
2059 offLine = cchLine - 1;
2060 while (RT_C_IS_BLANK(pchLine[offLine]))
2061 offLine--;
2062
2063 if (pchLine[offLine] != '*')
2064 return false;
2065 offLine--;
2066 if (!RT_C_IS_BLANK(pchLine[offLine]))
2067 return false;
2068 offLine--;
2069 while (RT_C_IS_BLANK(pchLine[offLine]))
2070 offLine--;
2071 *pcchText = offLine - offText + 1;
2072
2073 /*
2074 * Third line closes the box.
2075 */
2076 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2077 if (cchLine < cchBox - 3)
2078 return false;
2079
2080 offLine = 0;
2081 if (RT_C_IS_BLANK(pchLine[0]))
2082 {
2083 *pfNeedFixing = true;
2084 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2085 }
2086 while (offLine < cchLine && pchLine[offLine] == '*')
2087 offLine++;
2088 if (offLine < cchBox - 4)
2089 return false;
2090
2091 if (pchLine[offLine] != '/')
2092 return false;
2093 offLine++;
2094
2095 if (offLine != cchBox)
2096 *pfNeedFixing = true;
2097
2098 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2099 offLine++;
2100 if (offLine != cchLine)
2101 return false;
2102
2103 return true;
2104}
2105
2106
2107/**
2108 * Flower box marker comments in C and C++ code.
2109 *
2110 * @returns true if modifications were made, false if not.
2111 * @param pIn The input stream.
2112 * @param pOut The output stream.
2113 * @param pSettings The settings.
2114 */
2115bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2116{
2117 if (!pSettings->fFixFlowerBoxMarkers)
2118 return false;
2119
2120 /*
2121 * Work thru the file line by line looking for flower box markers.
2122 */
2123 size_t cChanges = 0;
2124 size_t cBlankLines = 0;
2125 SCMEOL enmEol;
2126 size_t cchLine;
2127 const char *pchLine;
2128 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2129 {
2130 /*
2131 * Get a likely match for a first line.
2132 */
2133 if ( pchLine[0] == '/'
2134 && cchLine > 20
2135 && pchLine[1] == '*'
2136 && pchLine[2] == '*'
2137 && pchLine[3] == '*')
2138 {
2139 size_t const offSaved = ScmStreamTell(pIn);
2140 char const *pchText;
2141 size_t cchText;
2142 bool fNeedFixing;
2143 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
2144 &pchText, &cchText, &fNeedFixing);
2145 if ( fIsFlowerBoxSection
2146 && ( fNeedFixing
2147 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
2148 {
2149 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
2150 {
2151 ScmStreamPutEol(pOut, enmEol);
2152 cBlankLines++;
2153 }
2154
2155 ScmStreamPutCh(pOut, '/');
2156 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2157 ScmStreamPutEol(pOut, enmEol);
2158
2159 static const char s_szLead[] = "* ";
2160 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
2161 ScmStreamWrite(pOut, pchText, cchText);
2162 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
2163 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
2164 ScmStreamPutCh(pOut, '*');
2165 ScmStreamPutEol(pOut, enmEol);
2166
2167 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2168 ScmStreamPutCh(pOut, '/');
2169 ScmStreamPutEol(pOut, enmEol);
2170
2171 cChanges++;
2172 cBlankLines = 0;
2173 continue;
2174 }
2175
2176 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
2177 if (RT_FAILURE(rc))
2178 return false;
2179 }
2180
2181 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2182 if (RT_FAILURE(rc))
2183 return false;
2184
2185 /* Do blank line accounting so we can ensure at least two blank lines
2186 before each section marker. */
2187 if (!isBlankLine(pchLine, cchLine))
2188 cBlankLines = 0;
2189 else
2190 cBlankLines++;
2191 }
2192 if (cChanges > 0)
2193 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
2194 return cChanges != 0;
2195}
2196
2197
2198/**
2199 * Looks for the start of a todo comment.
2200 *
2201 * @returns Offset into the line of the comment start sequence.
2202 * @param pchLine The line to search.
2203 * @param cchLineBeforeTodo The length of the line before the todo.
2204 * @param pfSameLine Indicates whether it's refering to a statemtn on
2205 * the same line comment (true), or the next
2206 * statement (false).
2207 */
2208static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
2209{
2210 *pfSameLine = false;
2211
2212 /* Skip one '@' or '\\'. */
2213 char ch;
2214 if ( cchLineBeforeTodo > 2
2215 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
2216 || ch == '\\' ) )
2217 cchLineBeforeTodo--;
2218
2219 /* Skip blanks. */
2220 while ( cchLineBeforeTodo > 2
2221 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
2222 cchLineBeforeTodo--;
2223
2224 /* Look for same line indicator. */
2225 if ( cchLineBeforeTodo > 0
2226 && pchLine[cchLineBeforeTodo - 1] == '<')
2227 {
2228 *pfSameLine = true;
2229 cchLineBeforeTodo--;
2230 }
2231
2232 /* Skip *s */
2233 while ( cchLineBeforeTodo > 1
2234 && pchLine[cchLineBeforeTodo - 1] == '*')
2235 cchLineBeforeTodo--;
2236
2237 /* Do we have a comment opening sequence. */
2238 if ( cchLineBeforeTodo > 0
2239 && pchLine[cchLineBeforeTodo - 1] == '/'
2240 && ( ( cchLineBeforeTodo >= 2
2241 && pchLine[cchLineBeforeTodo - 2] == '/')
2242 || pchLine[cchLineBeforeTodo] == '*'))
2243 {
2244 /* Skip slashes at the start. */
2245 while ( cchLineBeforeTodo > 0
2246 && pchLine[cchLineBeforeTodo - 1] == '/')
2247 cchLineBeforeTodo--;
2248
2249 return cchLineBeforeTodo;
2250 }
2251
2252 return ~(size_t)0;
2253}
2254
2255
2256/**
2257 * Looks for a TODO or todo in the given line.
2258 *
2259 * @returns Offset into the line of found, ~(size_t)0 if not.
2260 * @param pchLine The line to search.
2261 * @param cchLine The length of the line.
2262 */
2263static size_t findTodo(char const *pchLine, size_t cchLine)
2264{
2265 if (cchLine >= 4 + 2)
2266 {
2267 /* We don't search the first to chars because we need the start of a comment.
2268 Also, skip the last three chars since we need at least four for a match. */
2269 size_t const cchLineT = cchLine - 3;
2270 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
2271 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
2272 {
2273 for (size_t off = 2; off < cchLineT; off++)
2274 {
2275 char ch = pchLine[off];
2276 if ( ( ch != 't'
2277 && ch != 'T')
2278 || ( (ch = pchLine[off + 1]) != 'o'
2279 && ch != 'O')
2280 || ( (ch = pchLine[off + 2]) != 'd'
2281 && ch != 'D')
2282 || ( (ch = pchLine[off + 3]) != 'o'
2283 && ch != 'O')
2284 || ( off + 4 != cchLine
2285 && (ch = pchLine[off + 4]) != ' '
2286 && ch != '\t'
2287 && ch != ':' /** @todo */
2288 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
2289 ) )
2290 { /* not a hit - likely */ }
2291 else
2292 return off;
2293 }
2294 }
2295 }
2296 return ~(size_t)0;
2297}
2298
2299
2300/**
2301 * Flower box marker comments in C and C++ code.
2302 *
2303 * @returns true if modifications were made, false if not.
2304 * @param pIn The input stream.
2305 * @param pOut The output stream.
2306 * @param pSettings The settings.
2307 */
2308bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2309{
2310 if (!pSettings->fFixTodos)
2311 return false;
2312
2313 /*
2314 * Work thru the file line by line looking for the start of todo comments.
2315 */
2316 size_t cChanges = 0;
2317 SCMEOL enmEol;
2318 size_t cchLine;
2319 const char *pchLine;
2320 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2321 {
2322 /*
2323 * Look for the word 'todo' in the line. We're currently only trying
2324 * to catch comments starting with the word todo and adjust the start of
2325 * the doxygen statement.
2326 */
2327 size_t offTodo = findTodo(pchLine, cchLine);
2328 if ( offTodo != ~(size_t)0
2329 && offTodo >= 2)
2330 {
2331 /* Work backwards to find the start of the comment. */
2332 bool fSameLine = false;
2333 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
2334 if (offCommentStart != ~(size_t)0)
2335 {
2336 char szNew[64];
2337 size_t cchNew = 0;
2338 szNew[cchNew++] = '/';
2339 szNew[cchNew++] = pchLine[offCommentStart + 1];
2340 szNew[cchNew++] = pchLine[offCommentStart + 1];
2341 if (fSameLine)
2342 szNew[cchNew++] = '<';
2343 szNew[cchNew++] = ' ';
2344 szNew[cchNew++] = '@';
2345 szNew[cchNew++] = 't';
2346 szNew[cchNew++] = 'o';
2347 szNew[cchNew++] = 'd';
2348 szNew[cchNew++] = 'o';
2349
2350 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
2351 but need to take into account that we might be at the end of the line before
2352 adding the space. */
2353 size_t offTodoAfter = offTodo + 4;
2354 if ( offTodoAfter < cchLine
2355 && pchLine[offTodoAfter] == ':')
2356 offTodoAfter++;
2357 if ( offTodoAfter < cchLine
2358 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
2359 offTodoAfter++;
2360 if (offTodoAfter < cchLine)
2361 szNew[cchNew++] = ' ';
2362
2363 /* Write it out. */
2364 ScmStreamWrite(pOut, pchLine, offCommentStart);
2365 ScmStreamWrite(pOut, szNew, cchNew);
2366 if (offTodoAfter < cchLine)
2367 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
2368 ScmStreamPutEol(pOut, enmEol);
2369
2370 /* Check whether we actually made any changes. */
2371 if ( cchNew != offTodoAfter - offCommentStart
2372 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
2373 cChanges++;
2374 continue;
2375 }
2376 }
2377
2378 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2379 if (RT_FAILURE(rc))
2380 return false;
2381 }
2382 if (cChanges > 0)
2383 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
2384 return cChanges != 0;
2385}
2386
2387
2388/**
2389 * Rewrite a C/C++ source or header file.
2390 *
2391 * @returns true if modifications were made, false if not.
2392 * @param pIn The input stream.
2393 * @param pOut The output stream.
2394 * @param pSettings The settings.
2395 *
2396 * @todo
2397 *
2398 * Ideas for C/C++:
2399 * - space after if, while, for, switch
2400 * - spaces in for (i=0;i<x;i++)
2401 * - complex conditional, bird style.
2402 * - remove unnecessary parentheses.
2403 * - sort defined RT_OS_*|| and RT_ARCH
2404 * - sizeof without parenthesis.
2405 * - defined without parenthesis.
2406 * - trailing spaces.
2407 * - parameter indentation.
2408 * - space after comma.
2409 * - while (x--); -> multi line + comment.
2410 * - else statement;
2411 * - space between function and left parenthesis.
2412 * - TODO, XXX, @todo cleanup.
2413 * - Space before/after '*'.
2414 * - ensure new line at end of file.
2415 * - Indentation of precompiler statements (#ifdef, #defines).
2416 * - space between functions.
2417 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
2418 */
2419bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2420{
2421
2422 RT_NOREF4(pState, pIn, pOut, pSettings);
2423 return false;
2424}
2425
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