VirtualBox

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

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

scm: check svn:sync-process on files and parent dir.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 93.9 KB
Line 
1/* $Id: scmrw.cpp 70834 2018-01-31 14:48:21Z 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", pszParentSyncProcess);
1021 RTStrFree(pszParentSyncProcess);
1022 }
1023 else if (rc == VERR_NOT_FOUND)
1024 ScmError(pState, VERR_NOT_FOUND, "svn:sync-process=export, but parent directory is not exported!\n");
1025 else
1026 ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
1027 }
1028 else if (strcmp(pszSyncProcess, "ignore") != 0)
1029 ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
1030 RTStrFree(pszSyncProcess);
1031 }
1032 else if (rc != VERR_NOT_FOUND)
1033 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1034
1035 return false;
1036}
1037
1038/**
1039 * Compares two strings word-by-word, ignoring spaces, punctuation and case.
1040 *
1041 * Assumes ASCII strings.
1042 *
1043 * @returns true if they match, false if not.
1044 * @param psz1 The first string. This is typically the known one.
1045 * @param psz2 The second string. This is typically the unknown one,
1046 * which is why we return a next pointer for this one.
1047 * @param ppsz2Next Where to return the next part of the 2nd string. If
1048 * this is NULL, the whole string must match.
1049 */
1050static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
1051{
1052 for (;;)
1053 {
1054 /* Try compare raw strings first. */
1055 char ch1 = *psz1;
1056 char ch2 = *psz2;
1057 if ( ch1 == ch2
1058 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
1059 {
1060 if (ch1)
1061 {
1062 psz1++;
1063 psz2++;
1064 }
1065 else
1066 {
1067 if (ppsz2Next)
1068 *ppsz2Next = psz2;
1069 return true;
1070 }
1071 }
1072 else
1073 {
1074 /* Try skip spaces an punctuation. */
1075 while ( RT_C_IS_SPACE(ch1)
1076 || RT_C_IS_PUNCT(ch1))
1077 ch1 = *++psz1;
1078
1079 if (ch1 == '\0' && ppsz2Next)
1080 {
1081 *ppsz2Next = psz2;
1082 return true;
1083 }
1084
1085 while ( RT_C_IS_SPACE(ch2)
1086 || RT_C_IS_PUNCT(ch2))
1087 ch2 = *++psz2;
1088
1089 if ( ch1 != ch2
1090 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
1091 {
1092 if (ppsz2Next)
1093 *ppsz2Next = psz2;
1094 return false;
1095 }
1096 }
1097 }
1098}
1099
1100/**
1101 * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
1102 * and case.
1103 *
1104 * @returns true if found, false if not.
1105 * @param pszText The haystack to search in.
1106 * @param cchText The length @a pszText.
1107 * @param pszFragment The needle to search for.
1108 * @param ppszStart Where to return the address in @a pszText where
1109 * the fragment was found. Optional.
1110 * @param ppszNext Where to return the pointer to the first char in
1111 * @a pszText after the fragment. Optional.
1112 *
1113 * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
1114 * This character must not be space or punctuation.
1115 */
1116static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
1117 const char **ppszStart, const char **ppszNext)
1118{
1119 Assert(!((unsigned)*pszFragment & 0x80));
1120 Assert(pszText[cchText] == '\0');
1121 Assert(!RT_C_IS_BLANK(*pszFragment));
1122 Assert(!RT_C_IS_PUNCT(*pszFragment));
1123
1124 char chLower = RT_C_TO_LOWER(*pszFragment);
1125 char chUpper = RT_C_TO_UPPER(*pszFragment);
1126 for (;;)
1127 {
1128 const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
1129 const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
1130 if (!pszHit && !pszHit2)
1131 {
1132 if (ppszStart)
1133 *ppszStart = NULL;
1134 if (ppszNext)
1135 *ppszNext = NULL;
1136 return false;
1137 }
1138
1139 if ( pszHit == NULL
1140 || ( pszHit2 != NULL
1141 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
1142 pszHit = pszHit2;
1143
1144 const char *pszNext;
1145 if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
1146 {
1147 if (ppszStart)
1148 *ppszStart = pszHit;
1149 if (ppszNext)
1150 *ppszNext = pszNext;
1151 return true;
1152 }
1153
1154 cchText -= pszHit - pszText + 1;
1155 pszText = pszHit + 1;
1156 }
1157}
1158
1159
1160/**
1161 * Counts the number of lines in the given substring.
1162 *
1163 * @returns The number of lines.
1164 * @param psz The start of the substring.
1165 * @param cch The length of the substring.
1166 */
1167static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
1168{
1169 uint32_t cLines = 0;
1170 for (;;)
1171 {
1172 const char *pszEol = (const char *)memchr(psz, '\n', cch);
1173 if (pszEol)
1174 cLines++;
1175 else
1176 return cLines + (*psz != '\0');
1177 cch -= pszEol + 1 - psz;
1178 if (!cch)
1179 return cLines;
1180 psz = pszEol + 1;
1181 }
1182}
1183
1184
1185/**
1186 * Comment parser callback for locating copyright and license.
1187 */
1188static DECLCALLBACK(int)
1189rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
1190{
1191 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
1192 Assert(strlen(pszBody) == cchBody);
1193 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
1194 ScmVerbose(pState->pState, 5,
1195 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
1196 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
1197 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
1198
1199 pState->cComments++;
1200
1201 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
1202
1203 /*
1204 * Look for a 'contributed by' or 'includes contributions from' line, these
1205 * comes first when present.
1206 */
1207 const char *pchContributedBy = NULL;
1208 size_t cchContributedBy = 0;
1209 size_t cBlankLinesAfterContributedBy = 0;
1210 if ( pState->pszContributedBy == NULL
1211 && ( pState->iLineCopyright == UINT32_MAX
1212 || pState->iLineLicense == UINT32_MAX)
1213 && ( ( cchBody > sizeof("Contributed by")
1214 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
1215 || ( cchBody > sizeof("Includes contributions from")
1216 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
1217 {
1218 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1219 while (pszNextLine && pszNextLine[1] != '\n')
1220 pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
1221 if (pszNextLine)
1222 {
1223 pchContributedBy = pszBody;
1224 cchContributedBy = pszNextLine - pszBody;
1225
1226 /* Skip the copyright line and any blank lines following it. */
1227 cchBody -= cchContributedBy + 1;
1228 pszBody = pszNextLine + 1;
1229 iLine += 1;
1230 while (*pszBody == '\n')
1231 {
1232 pszBody++;
1233 cchBody--;
1234 iLine++;
1235 cBlankLinesAfterContributedBy++;
1236 }
1237 }
1238 }
1239
1240 /*
1241 * Look for the copyright line.
1242 */
1243 bool fFoundCopyright = false;
1244 uint32_t cBlankLinesAfterCopyright = 0;
1245 if ( pState->iLineCopyright == UINT32_MAX
1246 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
1247 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
1248 {
1249 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1250
1251 /* Oracle copyright? */
1252 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
1253 while (RT_C_IS_SPACE(pszEnd[-1]))
1254 pszEnd--;
1255 if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
1256 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1257 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
1258 {
1259 /* Parse out the year(s). */
1260 const char *psz = pszBody + sizeof("copyright");
1261 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
1262 psz++;
1263 if (RT_C_IS_DIGIT(*psz))
1264 {
1265 char *pszNext;
1266 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
1267 if ( RT_SUCCESS(rc)
1268 && rc != VWRN_NUMBER_TOO_BIG
1269 && rc != VWRN_NEGATIVE_UNSIGNED)
1270 {
1271 if ( pState->uFirstYear < 1975
1272 || pState->uFirstYear > 3000)
1273 {
1274 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
1275 pState->uFirstYear, pszEnd - pszBody, pszBody);
1276 pState->uFirstYear = UINT32_MAX;
1277 }
1278
1279 while (RT_C_IS_SPACE(*pszNext))
1280 pszNext++;
1281 if (*pszNext == '-')
1282 {
1283 do
1284 pszNext++;
1285 while (RT_C_IS_SPACE(*pszNext));
1286 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
1287 if ( RT_SUCCESS(rc)
1288 && rc != VWRN_NUMBER_TOO_BIG
1289 && rc != VWRN_NEGATIVE_UNSIGNED)
1290 {
1291 if ( pState->uLastYear < 1975
1292 || pState->uLastYear > 3000)
1293 {
1294 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
1295 pState->uLastYear, pszEnd - pszBody, pszBody);
1296 pState->uLastYear = UINT32_MAX;
1297 }
1298 else if (pState->uFirstYear > pState->uLastYear)
1299 {
1300 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
1301 uint32_t iTmp = pState->uLastYear;
1302 pState->uLastYear = pState->uFirstYear;
1303 pState->uFirstYear = iTmp;
1304 }
1305 }
1306 else
1307 {
1308 pState->uLastYear = UINT32_MAX;
1309 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1310 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1311 }
1312 }
1313 else if (*pszNext != g_szCopyrightHolder[0])
1314 ScmError(pState->pState, VERR_PARSE_ERROR,
1315 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1316 else
1317 pState->uLastYear = pState->uFirstYear;
1318 }
1319 else
1320 {
1321 pState->uFirstYear = UINT32_MAX;
1322 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1323 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1324 }
1325 }
1326
1327 /* The copyright comment must come before the license. */
1328 if (pState->iLineLicense != UINT32_MAX)
1329 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1330 iLine, pState->iLineLicense);
1331
1332 /* In C/C++ code, this must be a multiline comment. While in python it
1333 must be a */
1334 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1335 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1336 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1337 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1338
1339 /* The copyright must be followed by the license. */
1340 if (!pszNextLine)
1341 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1342
1343 /* Quit if we've flagged a failure. */
1344 if (RT_FAILURE(pState->pState->rc))
1345 return VERR_CALLBACK_RETURN;
1346
1347 /* Check if it's well formed and up to date. */
1348 char szWellFormed[256];
1349 size_t cchWellFormed;
1350 if (pState->uFirstYear == pState->uLastYear)
1351 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1352 pState->uFirstYear, g_szCopyrightHolder);
1353 else
1354 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1355 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1356 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1357 pState->iLineCopyright = iLine;
1358 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1359 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1360 if (!pState->fWellFormedCopyright)
1361 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1362
1363 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1364 if (pInfo->cBlankLinesBefore != 1)
1365 {
1366 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1367 pInfo->cBlankLinesBefore);
1368 pState->fWellFormedCopyright = false;
1369 }
1370
1371 /* If the comment doesn't start in column 1, trigger rewrite. */
1372 if (pInfo->offStart != 0)
1373 {
1374 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1375 pState->fWellFormedCopyright = false;
1376 /** @todo check that there isn't any code preceeding the comment. */
1377 }
1378
1379 if (pchContributedBy)
1380 {
1381 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1382 if (cBlankLinesAfterContributedBy != 1)
1383 {
1384 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1385 cBlankLinesAfterContributedBy);
1386 pState->fWellFormedCopyright = false;
1387 }
1388 }
1389
1390 fFoundCopyright = true;
1391 ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1392 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1393 }
1394 else
1395 ScmVerbose(pState->pState, 3, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1396
1397 if (!pszNextLine)
1398 return VINF_SUCCESS;
1399
1400 /* Skip the copyright line and any blank lines following it. */
1401 cchBody -= pszNextLine - pszBody + 1;
1402 pszBody = pszNextLine + 1;
1403 iLine += 1;
1404 while (*pszBody == '\n')
1405 {
1406 pszBody++;
1407 cchBody--;
1408 iLine++;
1409 cBlankLinesAfterCopyright++;
1410 }
1411
1412 /*
1413 * If we have a based-on-mit scenario, check for the lead in now and
1414 * complain if not found.
1415 */
1416 if ( fFoundCopyright
1417 && pState->enmLicenceOpt == kScmLicense_BasedOnMit
1418 && pState->iLineLicense == UINT32_MAX)
1419 {
1420 if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
1421 {
1422 /* Take down a comment area which goes up to 'this file is based on'.
1423 The license line and length isn't used but gets set to cover the current line. */
1424 pState->iLineComment = pInfo->iLineStart;
1425 pState->cLinesComment = iLine - pInfo->iLineStart;
1426 pState->iLineLicense = iLine;
1427 pState->cLinesLicense = 1;
1428 pState->fExternalLicense = true;
1429 pState->fIsCorrectLicense = true;
1430 pState->fWellFormedLicense = true;
1431
1432 /* Check if we've got a MIT a license here or not. */
1433 pState->pCurrentLicense = NULL;
1434 do
1435 {
1436 const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
1437 if (!pszEol || pszEol[1] == '\0')
1438 {
1439 pszBody += cchBody;
1440 cchBody = 0;
1441 break;
1442 }
1443 cchBody -= pszEol - pszBody + 1;
1444 pszBody = pszEol + 1;
1445 iLine++;
1446
1447 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1448 {
1449 const char *pszNext;
1450 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1451 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1452 {
1453 pState->pCurrentLicense = pCur;
1454 break;
1455 }
1456 }
1457 } while (!pState->pCurrentLicense);
1458 if (!pState->pCurrentLicense)
1459 ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
1460 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
1461 ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
1462 pState->pCurrentLicense->psz);
1463 }
1464 else
1465 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
1466 return VINF_SUCCESS;
1467 }
1468 }
1469
1470 /*
1471 * Look for LGPL like text in the comment.
1472 */
1473 if (pState->fCheckforLgpl && cchBody > 128)
1474 {
1475 /* We look for typical LGPL notices. */
1476 if (pState->iLineLgplNotice == UINT32_MAX)
1477 {
1478 static const char * const s_apszFragments[] =
1479 {
1480 "under the terms of the GNU Lesser General Public License",
1481 };
1482 for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
1483 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
1484 {
1485 pState->iLineLgplNotice = iLine;
1486 pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
1487 ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
1488 break;
1489 }
1490 }
1491
1492 if ( pState->iLineLgplDisclaimer == UINT32_MAX
1493 && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
1494 {
1495 pState->iLineLgplDisclaimer = iLine;
1496 ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
1497 }
1498 }
1499
1500 /*
1501 * Look for the license text.
1502 */
1503 if (pState->iLineLicense == UINT32_MAX)
1504 {
1505 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1506 {
1507 const char *pszNext;
1508 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1509 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1510 {
1511 while ( RT_C_IS_SPACE(*pszNext)
1512 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1513 pszNext++;
1514
1515 uint32_t cDashes = 0;
1516 while (*pszNext == '-')
1517 cDashes++, pszNext++;
1518 bool fExternal = cDashes > 10;
1519
1520 if ( *pszNext == '\0'
1521 || fExternal)
1522 {
1523 /* In C/C++ code, this must be a multiline comment. While in python it
1524 must be a */
1525 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1526 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1527 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1528 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1529
1530 /* Quit if we've flagged a failure. */
1531 if (RT_FAILURE(pState->pState->rc))
1532 return VERR_CALLBACK_RETURN;
1533
1534 /* Record it. */
1535 pState->iLineLicense = iLine;
1536 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
1537 pState->pCurrentLicense = pCur;
1538 pState->fExternalLicense = fExternal;
1539 pState->fIsCorrectLicense = pState->fOpenSource
1540 ? pCur == pState->pExpectedLicense
1541 : pCur->enmType == kScmLicenseType_Confidential;
1542 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1543 if (!pState->fWellFormedLicense)
1544 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1545
1546 /* If there was more than one blank line between the copyright and the
1547 license text, extend the license text area and force a rewrite of it. */
1548 if (cBlankLinesAfterCopyright > 1)
1549 {
1550 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1551 cBlankLinesAfterCopyright);
1552 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1553 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1554 pState->fWellFormedLicense = false;
1555 }
1556
1557 /* If there was more than one blank line after the license, trigger a rewrite. */
1558 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1559 {
1560 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1561 pInfo->cBlankLinesAfter);
1562 pState->fWellFormedLicense = false;
1563 }
1564
1565 /** @todo Check that the last comment line doesn't have any code on it. */
1566 /** @todo Check that column 2 contains '*' for C/C++ files. */
1567
1568 ScmVerbose(pState->pState, 3,
1569 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1570 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1571 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1572 pState->fExternalLicense, pState->fOpenSource);
1573
1574 if (fFoundCopyright)
1575 {
1576 pState->iLineComment = pInfo->iLineStart;
1577 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1578 - pInfo->iLineStart;
1579 }
1580 else
1581 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1582 break;
1583 }
1584 }
1585 }
1586 }
1587
1588 if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
1589 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1590
1591 /*
1592 * Stop looking for stuff after 100 comments.
1593 */
1594 if (pState->cComments > 100)
1595 return VERR_CALLBACK_RETURN;
1596 return VINF_SUCCESS;
1597}
1598
1599/**
1600 * Writes comment body text.
1601 *
1602 * @returns Stream status.
1603 * @param pOut The output stream.
1604 * @param pszText The text to write.
1605 * @param cchText The length of the text.
1606 * @param enmCommentStyle The comment style.
1607 * @param enmEol The EOL style.
1608 */
1609static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
1610 SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
1611{
1612 Assert(pszText[cchText - 1] == '\n');
1613 Assert(pszText[cchText - 2] != '\n');
1614 NOREF(cchText);
1615 do
1616 {
1617 const char *pszEol = strchr(pszText, '\n');
1618 if (pszEol != pszText)
1619 {
1620 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1621 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1622 ScmStreamWrite(pOut, pszText, pszEol - pszText);
1623 ScmStreamPutEol(pOut, enmEol);
1624 }
1625 else
1626 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1627 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1628 pszText = pszEol + 1;
1629 } while (*pszText != '\0');
1630 return ScmStreamGetStatus(pOut);
1631}
1632
1633
1634/**
1635 * Updates the copyright year and/or license text.
1636 *
1637 * @returns true if modifications were made, false if not.
1638 * @param pState The rewriter state.
1639 * @param pIn The input stream.
1640 * @param pOut The output stream.
1641 * @param pSettings The settings.
1642 * @param enmCommentStyle The comment style used by the file.
1643 */
1644static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1645 SCMCOMMENTSTYLE enmCommentStyle)
1646{
1647 if ( !pSettings->fUpdateCopyrightYear
1648 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1649 return false;
1650
1651 /*
1652 * Try locate the relevant comments.
1653 */
1654 SCMCOPYRIGHTINFO Info =
1655 {
1656 /*.pState = */ pState,
1657 /*.enmCommentStyle = */ enmCommentStyle,
1658
1659 /*.cComments = */ 0,
1660
1661 /*.pszContributedBy = */ NULL,
1662
1663 /*.iLineComment = */ UINT32_MAX,
1664 /*.cLinesComment = */ 0,
1665
1666 /*.iLineCopyright = */ UINT32_MAX,
1667 /*.uFirstYear = */ UINT32_MAX,
1668 /*.uLastYear = */ UINT32_MAX,
1669 /*.fWellFormedCopyright = */ false,
1670 /*.fUpToDateCopyright = */ false,
1671
1672 /*.fOpenSource = */ true,
1673 /*.pExpectedLicense = */ NULL,
1674 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
1675 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
1676 ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1677 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1678 /*.iLineLicense = */ UINT32_MAX,
1679 /*.cLinesLicense = */ 0,
1680 /*.pCurrentLicense = */ NULL,
1681 /*.fIsCorrectLicense = */ false,
1682 /*.fWellFormedLicense = */ false,
1683 /*.fExternalLicense = */ false,
1684
1685 /*.fCheckForLgpl = */ true,
1686 /*.iLineLgplNotice = */ UINT32_MAX,
1687 /*.iLineAfterLgplComment = */ UINT32_MAX,
1688 /*.iLineLgplDisclaimer = */ UINT32_MAX,
1689 };
1690
1691 /* Figure Info.fOpenSource and the desired license: */
1692 char *pszSyncProcess;
1693 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1694 if (RT_SUCCESS(rc))
1695 {
1696 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1697 RTStrFree(pszSyncProcess);
1698 }
1699 else if (rc == VERR_NOT_FOUND)
1700 Info.fOpenSource = false;
1701 else
1702 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1703
1704 Info.pExpectedLicense = Info.paLicenses;
1705 if (Info.fOpenSource)
1706 {
1707 if ( pSettings->enmUpdateLicense != kScmLicense_Mit
1708 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1709 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1710 Info.pExpectedLicense++;
1711 else
1712 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
1713 }
1714 else
1715 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1716 Info.pExpectedLicense++;
1717
1718 /* Scan the comments. */
1719 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1720 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1721 && RT_SUCCESS(pState->rc))
1722 {
1723 /*
1724 * Do conformity checks.
1725 */
1726 bool fAddLgplDisclaimer = false;
1727 if (Info.fCheckforLgpl)
1728 {
1729 if ( Info.iLineLgplNotice != UINT32_MAX
1730 && Info.iLineLgplDisclaimer == UINT32_MAX)
1731 {
1732 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
1733 ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
1734 Info.iLineLgplNotice + 1);
1735 else
1736 {
1737 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
1738 fAddLgplDisclaimer = true;
1739 }
1740 }
1741 else if ( Info.iLineLgplNotice == UINT32_MAX
1742 && Info.iLineLgplDisclaimer != UINT32_MAX)
1743 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
1744 Info.iLineLgplDisclaimer + 1);
1745 }
1746
1747 if (!pSettings->fExternalCopyright)
1748 {
1749 if (Info.iLineCopyright == UINT32_MAX)
1750 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1751 if (Info.iLineLicense == UINT32_MAX)
1752 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1753 }
1754 else if (Info.iLineCopyright != UINT32_MAX)
1755 ScmError(pState, VERR_NOT_FOUND,
1756 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1757 Info.iLineCopyright + 1);
1758
1759
1760 if (RT_SUCCESS(pState->rc))
1761 {
1762 /*
1763 * Do we need to make any changes?
1764 */
1765 bool fUpdateCopyright = !pSettings->fExternalCopyright
1766 && ( !Info.fWellFormedCopyright
1767 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
1768 bool fUpdateLicense = !pSettings->fExternalCopyright
1769 && Info.enmLicenceOpt != kScmLicense_LeaveAlone
1770 && ( !Info.fWellFormedLicense
1771 || !Info.fIsCorrectLicense);
1772 if ( fUpdateCopyright
1773 || fUpdateLicense
1774 || fAddLgplDisclaimer)
1775 {
1776 Assert(Info.iLineComment != UINT32_MAX);
1777 Assert(Info.cLinesComment > 0);
1778
1779 /*
1780 * Okay, do the work.
1781 */
1782 ScmStreamRewindForReading(pIn);
1783
1784 if (pSettings->fUpdateCopyrightYear)
1785 Info.uLastYear = g_uYear;
1786
1787 uint32_t iLine = 0;
1788 SCMEOL enmEol;
1789 size_t cchLine;
1790 const char *pchLine;
1791 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1792 {
1793 if ( iLine == Info.iLineComment
1794 && (fUpdateCopyright || fUpdateLicense) )
1795 {
1796 /* Leading blank line. */
1797 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1798 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1799
1800 /* Contributed by someone? */
1801 if (Info.pszContributedBy)
1802 {
1803 const char *psz = Info.pszContributedBy;
1804 for (;;)
1805 {
1806 const char *pszEol = strchr(psz, '\n');
1807 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
1808 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1809 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1810 ScmStreamWrite(pOut, psz, cchContribLine);
1811 ScmStreamPutEol(pOut, enmEol);
1812 if (!pszEol)
1813 break;
1814 psz = pszEol + 1;
1815 }
1816
1817 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1818 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1819 }
1820
1821 /* Write the copyright comment line. */
1822 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1823 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1824
1825 char szCopyright[256];
1826 size_t cchCopyright;
1827 if (Info.uFirstYear == Info.uLastYear)
1828 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1829 Info.uFirstYear, g_szCopyrightHolder);
1830 else
1831 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1832 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1833
1834 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1835 ScmStreamPutEol(pOut, enmEol);
1836
1837 if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1838 {
1839 /* Blank line separating the two. */
1840 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1841 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1842
1843 /* Write the license text. */
1844 scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
1845 enmCommentStyle, enmEol);
1846
1847 /* Final comment line. */
1848 if (!Info.fExternalLicense)
1849 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1850 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1851 }
1852 else
1853 Assert(Info.fExternalLicense);
1854
1855 /* Skip the copyright and license text in the input file. */
1856 rc = ScmStreamGetStatus(pOut);
1857 if (RT_SUCCESS(rc))
1858 {
1859 iLine = Info.iLineComment + Info.cLinesComment;
1860 rc = ScmStreamSeekByLine(pIn, iLine);
1861 }
1862 }
1863 /*
1864 * Add LGPL disclaimer?
1865 */
1866 else if ( iLine == Info.iLineAfterLgplComment
1867 && fAddLgplDisclaimer)
1868 {
1869 ScmStreamPutEol(pOut, enmEol);
1870 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1871 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1872 scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
1873 enmCommentStyle, enmEol);
1874 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1875 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1876
1877 /* put the actual line */
1878 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1879 iLine++;
1880 }
1881 else
1882 {
1883 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1884 iLine++;
1885 }
1886 if (RT_FAILURE(rc))
1887 {
1888 RTStrFree(Info.pszContributedBy);
1889 return false;
1890 }
1891 } /* for each source line */
1892
1893 RTStrFree(Info.pszContributedBy);
1894 return true;
1895 }
1896 }
1897 }
1898 else
1899 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1900 NOREF(pState); NOREF(pOut);
1901 RTStrFree(Info.pszContributedBy);
1902 return false;
1903}
1904
1905
1906/** Copyright updater for C-style comments. */
1907bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1908{
1909 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
1910}
1911
1912/** Copyright updater for hash-prefixed comments. */
1913bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1914{
1915 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
1916}
1917
1918/** Copyright updater for REM-prefixed comments. */
1919bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1920{
1921 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
1922}
1923
1924/** Copyright updater for python comments. */
1925bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1926{
1927 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
1928}
1929
1930/** Copyright updater for semicolon-prefixed comments. */
1931bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1932{
1933 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
1934}
1935
1936/** Copyright updater for sql comments. */
1937bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1938{
1939 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
1940}
1941
1942/** Copyright updater for tick-prefixed comments. */
1943bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1944{
1945 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
1946}
1947
1948
1949/**
1950 * Makefile.kup are empty files, enforce this.
1951 *
1952 * @returns true if modifications were made, false if not.
1953 * @param pIn The input stream.
1954 * @param pOut The output stream.
1955 * @param pSettings The settings.
1956 */
1957bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1958{
1959 RT_NOREF2(pOut, pSettings);
1960
1961 /* These files should be zero bytes. */
1962 if (pIn->cb == 0)
1963 return false;
1964 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
1965 return true;
1966}
1967
1968/**
1969 * Rewrite a kBuild makefile.
1970 *
1971 * @returns true if modifications were made, false if not.
1972 * @param pIn The input stream.
1973 * @param pOut The output stream.
1974 * @param pSettings The settings.
1975 *
1976 * @todo
1977 *
1978 * Ideas for Makefile.kmk and Config.kmk:
1979 * - sort if1of/ifn1of sets.
1980 * - line continuation slashes should only be preceded by one space.
1981 */
1982bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1983{
1984 RT_NOREF4(pState, pIn, pOut, pSettings);
1985 return false;
1986}
1987
1988
1989static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
1990 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
1991{
1992 *ppchText = NULL;
1993 *pcchText = 0;
1994 *pfNeedFixing = false;
1995
1996 /*
1997 * The first line.
1998 */
1999 if (pchLine[0] != '/')
2000 return false;
2001 size_t offLine = 1;
2002 while (offLine < cchLine && pchLine[offLine] == '*')
2003 offLine++;
2004 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
2005 return false;
2006 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2007 offLine++;
2008 if (offLine != cchLine)
2009 return false;
2010
2011 size_t const cchBox = cchLine;
2012 *pfNeedFixing = cchBox != cchWidth;
2013
2014 /*
2015 * The next line, extracting the text.
2016 */
2017 SCMEOL enmEol;
2018 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2019 if (cchLine < cchBox - 3)
2020 return false;
2021
2022 offLine = 0;
2023 if (RT_C_IS_BLANK(pchLine[0]))
2024 {
2025 *pfNeedFixing = true;
2026 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2027 }
2028
2029 if (pchLine[offLine] != '*')
2030 return false;
2031 offLine++;
2032
2033 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
2034 return false;
2035 offLine++;
2036
2037 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2038 offLine++;
2039 if (offLine >= cchLine)
2040 return false;
2041 if (!RT_C_IS_UPPER(pchLine[offLine]))
2042 return false;
2043
2044 if (offLine != 4 || cchLine != cchBox)
2045 *pfNeedFixing = true;
2046
2047 *ppchText = &pchLine[offLine];
2048 size_t const offText = offLine;
2049
2050 /* From the end now. */
2051 offLine = cchLine - 1;
2052 while (RT_C_IS_BLANK(pchLine[offLine]))
2053 offLine--;
2054
2055 if (pchLine[offLine] != '*')
2056 return false;
2057 offLine--;
2058 if (!RT_C_IS_BLANK(pchLine[offLine]))
2059 return false;
2060 offLine--;
2061 while (RT_C_IS_BLANK(pchLine[offLine]))
2062 offLine--;
2063 *pcchText = offLine - offText + 1;
2064
2065 /*
2066 * Third line closes the box.
2067 */
2068 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2069 if (cchLine < cchBox - 3)
2070 return false;
2071
2072 offLine = 0;
2073 if (RT_C_IS_BLANK(pchLine[0]))
2074 {
2075 *pfNeedFixing = true;
2076 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2077 }
2078 while (offLine < cchLine && pchLine[offLine] == '*')
2079 offLine++;
2080 if (offLine < cchBox - 4)
2081 return false;
2082
2083 if (pchLine[offLine] != '/')
2084 return false;
2085 offLine++;
2086
2087 if (offLine != cchBox)
2088 *pfNeedFixing = true;
2089
2090 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2091 offLine++;
2092 if (offLine != cchLine)
2093 return false;
2094
2095 return true;
2096}
2097
2098
2099/**
2100 * Flower box marker comments in C and C++ code.
2101 *
2102 * @returns true if modifications were made, false if not.
2103 * @param pIn The input stream.
2104 * @param pOut The output stream.
2105 * @param pSettings The settings.
2106 */
2107bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2108{
2109 if (!pSettings->fFixFlowerBoxMarkers)
2110 return false;
2111
2112 /*
2113 * Work thru the file line by line looking for flower box markers.
2114 */
2115 size_t cChanges = 0;
2116 size_t cBlankLines = 0;
2117 SCMEOL enmEol;
2118 size_t cchLine;
2119 const char *pchLine;
2120 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2121 {
2122 /*
2123 * Get a likely match for a first line.
2124 */
2125 if ( pchLine[0] == '/'
2126 && cchLine > 20
2127 && pchLine[1] == '*'
2128 && pchLine[2] == '*'
2129 && pchLine[3] == '*')
2130 {
2131 size_t const offSaved = ScmStreamTell(pIn);
2132 char const *pchText;
2133 size_t cchText;
2134 bool fNeedFixing;
2135 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
2136 &pchText, &cchText, &fNeedFixing);
2137 if ( fIsFlowerBoxSection
2138 && ( fNeedFixing
2139 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
2140 {
2141 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
2142 {
2143 ScmStreamPutEol(pOut, enmEol);
2144 cBlankLines++;
2145 }
2146
2147 ScmStreamPutCh(pOut, '/');
2148 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2149 ScmStreamPutEol(pOut, enmEol);
2150
2151 static const char s_szLead[] = "* ";
2152 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
2153 ScmStreamWrite(pOut, pchText, cchText);
2154 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
2155 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
2156 ScmStreamPutCh(pOut, '*');
2157 ScmStreamPutEol(pOut, enmEol);
2158
2159 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2160 ScmStreamPutCh(pOut, '/');
2161 ScmStreamPutEol(pOut, enmEol);
2162
2163 cChanges++;
2164 cBlankLines = 0;
2165 continue;
2166 }
2167
2168 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
2169 if (RT_FAILURE(rc))
2170 return false;
2171 }
2172
2173 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2174 if (RT_FAILURE(rc))
2175 return false;
2176
2177 /* Do blank line accounting so we can ensure at least two blank lines
2178 before each section marker. */
2179 if (!isBlankLine(pchLine, cchLine))
2180 cBlankLines = 0;
2181 else
2182 cBlankLines++;
2183 }
2184 if (cChanges > 0)
2185 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
2186 return cChanges != 0;
2187}
2188
2189
2190/**
2191 * Looks for the start of a todo comment.
2192 *
2193 * @returns Offset into the line of the comment start sequence.
2194 * @param pchLine The line to search.
2195 * @param cchLineBeforeTodo The length of the line before the todo.
2196 * @param pfSameLine Indicates whether it's refering to a statemtn on
2197 * the same line comment (true), or the next
2198 * statement (false).
2199 */
2200static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
2201{
2202 *pfSameLine = false;
2203
2204 /* Skip one '@' or '\\'. */
2205 char ch;
2206 if ( cchLineBeforeTodo > 2
2207 && ( (ch = pchLine[cchLineBeforeTodo - 1] == '@')
2208 || ch == '\\' ) )
2209 cchLineBeforeTodo--;
2210
2211 /* Skip blanks. */
2212 while ( cchLineBeforeTodo > 2
2213 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
2214 cchLineBeforeTodo--;
2215
2216 /* Look for same line indicator. */
2217 if ( cchLineBeforeTodo > 0
2218 && pchLine[cchLineBeforeTodo - 1] == '<')
2219 {
2220 *pfSameLine = true;
2221 cchLineBeforeTodo--;
2222 }
2223
2224 /* Skip *s */
2225 while ( cchLineBeforeTodo > 1
2226 && pchLine[cchLineBeforeTodo - 1] == '*')
2227 cchLineBeforeTodo--;
2228
2229 /* Do we have a comment opening sequence. */
2230 if ( cchLineBeforeTodo > 0
2231 && pchLine[cchLineBeforeTodo - 1] == '/'
2232 && ( ( cchLineBeforeTodo >= 2
2233 && pchLine[cchLineBeforeTodo - 2] == '/')
2234 || pchLine[cchLineBeforeTodo] == '*'))
2235 {
2236 /* Skip slashes at the start. */
2237 while ( cchLineBeforeTodo > 0
2238 && pchLine[cchLineBeforeTodo - 1] == '/')
2239 cchLineBeforeTodo--;
2240
2241 return cchLineBeforeTodo;
2242 }
2243
2244 return ~(size_t)0;
2245}
2246
2247
2248/**
2249 * Looks for a TODO or todo in the given line.
2250 *
2251 * @returns Offset into the line of found, ~(size_t)0 if not.
2252 * @param pchLine The line to search.
2253 * @param cchLine The length of the line.
2254 */
2255static size_t findTodo(char const *pchLine, size_t cchLine)
2256{
2257 if (cchLine >= 4 + 2)
2258 {
2259 /* We don't search the first to chars because we need the start of a comment.
2260 Also, skip the last three chars since we need at least four for a match. */
2261 size_t const cchLineT = cchLine - 3;
2262 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
2263 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
2264 {
2265 for (size_t off = 2; off < cchLineT; off++)
2266 {
2267 char ch = pchLine[off];
2268 if ( ( ch != 't'
2269 && ch != 'T')
2270 || ( (ch = pchLine[off + 1]) != 'o'
2271 && ch != 'O')
2272 || ( (ch = pchLine[off + 2]) != 'd'
2273 && ch != 'D')
2274 || ( (ch = pchLine[off + 3]) != 'o'
2275 && ch != 'O')
2276 || ( off + 4 != cchLine
2277 && (ch = pchLine[off + 4]) != ' '
2278 && ch != '\t'
2279 && ch != ':' /** @todo */
2280 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
2281 ) )
2282 { /* not a hit - likely */ }
2283 else
2284 return off;
2285 }
2286 }
2287 }
2288 return ~(size_t)0;
2289}
2290
2291
2292/**
2293 * Flower box marker comments in C and C++ code.
2294 *
2295 * @returns true if modifications were made, false if not.
2296 * @param pIn The input stream.
2297 * @param pOut The output stream.
2298 * @param pSettings The settings.
2299 */
2300bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2301{
2302 if (!pSettings->fFixTodos)
2303 return false;
2304
2305 /*
2306 * Work thru the file line by line looking for the start of todo comments.
2307 */
2308 size_t cChanges = 0;
2309 SCMEOL enmEol;
2310 size_t cchLine;
2311 const char *pchLine;
2312 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2313 {
2314 /*
2315 * Look for the word 'todo' in the line. We're currently only trying
2316 * to catch comments starting with the word todo and adjust the start of
2317 * the doxygen statement.
2318 */
2319 size_t offTodo = findTodo(pchLine, cchLine);
2320 if ( offTodo != ~(size_t)0
2321 && offTodo >= 2)
2322 {
2323 /* Work backwards to find the start of the comment. */
2324 bool fSameLine = false;
2325 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
2326 if (offCommentStart != ~(size_t)0)
2327 {
2328 char szNew[64];
2329 size_t cchNew = 0;
2330 szNew[cchNew++] = '/';
2331 szNew[cchNew++] = pchLine[offCommentStart + 1];
2332 szNew[cchNew++] = pchLine[offCommentStart + 1];
2333 if (fSameLine)
2334 szNew[cchNew++] = '<';
2335 szNew[cchNew++] = ' ';
2336 szNew[cchNew++] = '@';
2337 szNew[cchNew++] = 't';
2338 szNew[cchNew++] = 'o';
2339 szNew[cchNew++] = 'd';
2340 szNew[cchNew++] = 'o';
2341
2342 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
2343 but need to take into account that we might be at the end of the line before
2344 adding the space. */
2345 size_t offTodoAfter = offTodo + 4;
2346 if ( offTodoAfter < cchLine
2347 && pchLine[offTodoAfter] == ':')
2348 offTodoAfter++;
2349 if ( offTodoAfter < cchLine
2350 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
2351 offTodoAfter++;
2352 if (offTodoAfter < cchLine)
2353 szNew[cchNew++] = ' ';
2354
2355 /* Write it out. */
2356 ScmStreamWrite(pOut, pchLine, offCommentStart);
2357 ScmStreamWrite(pOut, szNew, cchNew);
2358 if (offTodoAfter < cchLine)
2359 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
2360 ScmStreamPutEol(pOut, enmEol);
2361
2362 /* Check whether we actually made any changes. */
2363 if ( cchNew != offTodoAfter - offCommentStart
2364 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
2365 cChanges++;
2366 continue;
2367 }
2368 }
2369
2370 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2371 if (RT_FAILURE(rc))
2372 return false;
2373 }
2374 if (cChanges > 0)
2375 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
2376 return cChanges != 0;
2377}
2378
2379
2380/**
2381 * Rewrite a C/C++ source or header file.
2382 *
2383 * @returns true if modifications were made, false if not.
2384 * @param pIn The input stream.
2385 * @param pOut The output stream.
2386 * @param pSettings The settings.
2387 *
2388 * @todo
2389 *
2390 * Ideas for C/C++:
2391 * - space after if, while, for, switch
2392 * - spaces in for (i=0;i<x;i++)
2393 * - complex conditional, bird style.
2394 * - remove unnecessary parentheses.
2395 * - sort defined RT_OS_*|| and RT_ARCH
2396 * - sizeof without parenthesis.
2397 * - defined without parenthesis.
2398 * - trailing spaces.
2399 * - parameter indentation.
2400 * - space after comma.
2401 * - while (x--); -> multi line + comment.
2402 * - else statement;
2403 * - space between function and left parenthesis.
2404 * - TODO, XXX, @todo cleanup.
2405 * - Space before/after '*'.
2406 * - ensure new line at end of file.
2407 * - Indentation of precompiler statements (#ifdef, #defines).
2408 * - space between functions.
2409 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
2410 */
2411bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2412{
2413
2414 RT_NOREF4(pState, pIn, pOut, pSettings);
2415 return false;
2416}
2417
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