VirtualBox

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

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

scm: another MIT variant (vbox_vendor_selector)

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

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