VirtualBox

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

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

scm: fix updating copyright/license blocks followed by original copyright+license.

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