VirtualBox

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

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

scm: license matching fix

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