VirtualBox

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

Last change on this file since 69503 was 69503, checked in by vboxsync, 7 years ago

scm: Added LGPL disclaimer checks.

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