VirtualBox

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

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

scm: 'Includes contributions from'

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.0 KB
Line 
1/* $Id: scmrw.cpp 69247 2017-10-24 18:59:47Z 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' or 'includes contributions from' line, these
906 * comes first when present.
907 */
908 const char *pchContributedBy = NULL;
909 size_t cchContributedBy = 0;
910 size_t cBlankLinesAfterContributedBy = 0;
911 if ( pState->pszContributedBy == NULL
912 && ( pState->iLineCopyright == UINT32_MAX
913 || pState->iLineLicense == UINT32_MAX)
914 && ( ( cchBody > sizeof("Contributed by")
915 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
916 || ( cchBody > sizeof("Includes contributions from")
917 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
918 {
919 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
920 if (pszNextLine)
921 {
922 pchContributedBy = pszBody;
923 cchContributedBy = pszNextLine - pszBody;
924
925 /* Skip the copyright line and any blank lines following it. */
926 cchBody -= cchContributedBy + 1;
927 pszBody = pszNextLine + 1;
928 iLine += 1;
929 while (*pszBody == '\n')
930 {
931 pszBody++;
932 cchBody--;
933 iLine++;
934 cBlankLinesAfterContributedBy++;
935 }
936 }
937 }
938
939 /*
940 * Look for the copyright line.
941 */
942 bool fFoundCopyright = false;
943 uint32_t cBlankLinesAfterCopyright = 0;
944 if ( pState->iLineCopyright == UINT32_MAX
945 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
946 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
947 {
948 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
949
950 /* Oracle copyright? */
951 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
952 while (RT_C_IS_SPACE(pszEnd[-1]))
953 pszEnd--;
954 if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
955 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
956 {
957 /* Parse out the year(s). */
958 const char *psz = pszBody + sizeof("copyright");
959 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
960 psz++;
961 if (RT_C_IS_DIGIT(*psz))
962 {
963 char *pszNext;
964 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
965 if ( RT_SUCCESS(rc)
966 && rc != VWRN_NUMBER_TOO_BIG
967 && rc != VWRN_NEGATIVE_UNSIGNED)
968 {
969 if ( pState->uFirstYear < 1975
970 || pState->uFirstYear > 3000)
971 {
972 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
973 pState->uFirstYear, pszEnd - pszBody, pszBody);
974 pState->uFirstYear = UINT32_MAX;
975 }
976
977 while (RT_C_IS_SPACE(*pszNext))
978 pszNext++;
979 if (*pszNext == '-')
980 {
981 do
982 pszNext++;
983 while (RT_C_IS_SPACE(*pszNext));
984 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
985 if ( RT_SUCCESS(rc)
986 && rc != VWRN_NUMBER_TOO_BIG
987 && rc != VWRN_NEGATIVE_UNSIGNED)
988 {
989 if ( pState->uLastYear < 1975
990 || pState->uLastYear > 3000)
991 {
992 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
993 pState->uLastYear, pszEnd - pszBody, pszBody);
994 pState->uLastYear = UINT32_MAX;
995 }
996 else if (pState->uFirstYear > pState->uLastYear)
997 {
998 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
999 uint32_t iTmp = pState->uLastYear;
1000 pState->uLastYear = pState->uFirstYear;
1001 pState->uFirstYear = iTmp;
1002 }
1003 }
1004 else
1005 {
1006 pState->uLastYear = UINT32_MAX;
1007 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1008 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1009 }
1010 }
1011 else if (*pszNext != g_szCopyrightHolder[0])
1012 ScmError(pState->pState, VERR_PARSE_ERROR,
1013 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1014 else
1015 pState->uLastYear = pState->uFirstYear;
1016 }
1017 else
1018 {
1019 pState->uFirstYear = UINT32_MAX;
1020 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1021 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1022 }
1023 }
1024
1025 /* The copyright comment must come before the license. */
1026 if (pState->iLineLicense != UINT32_MAX)
1027 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1028 iLine, pState->iLineLicense);
1029
1030 /* In C/C++ code, this must be a multiline comment. While in python it
1031 must be a */
1032 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1033 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1034 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1035 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1036
1037 /* The copyright must be followed by the license. */
1038 if (!pszNextLine)
1039 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1040
1041 /* Quit if we've flagged a failure. */
1042 if (RT_FAILURE(pState->pState->rc))
1043 return VERR_CALLBACK_RETURN;
1044
1045 /* Check if it's well formed and up to date. */
1046 char szWellFormed[256];
1047 size_t cchWellFormed;
1048 if (pState->uFirstYear == pState->uLastYear)
1049 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1050 pState->uFirstYear, g_szCopyrightHolder);
1051 else
1052 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1053 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1054 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1055 pState->iLineCopyright = iLine;
1056 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1057 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1058 if (!pState->fWellFormedCopyright)
1059 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1060
1061 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1062 if (pInfo->cBlankLinesBefore != 1)
1063 {
1064 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1065 pInfo->cBlankLinesBefore);
1066 pState->fWellFormedCopyright = false;
1067 }
1068
1069 /* If the comment doesn't start in column 1, trigger rewrite. */
1070 if (pInfo->offStart != 0)
1071 {
1072 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1073 pState->fWellFormedCopyright = false;
1074 /** @todo check that there isn't any code preceeding the comment. */
1075 }
1076
1077 if (pchContributedBy)
1078 {
1079 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1080 if (cBlankLinesAfterContributedBy != 1)
1081 {
1082 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1083 cBlankLinesAfterContributedBy);
1084 pState->fWellFormedCopyright = false;
1085 }
1086 }
1087
1088 fFoundCopyright = true;
1089 ScmVerbose(pState->pState, 2, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1090 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1091 }
1092 else
1093 ScmVerbose(pState->pState, 2, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1094
1095 if (!pszNextLine)
1096 return VINF_SUCCESS;
1097
1098 /* Skip the copyright line and any blank lines following it. */
1099 cchBody -= pszNextLine - pszBody + 1;
1100 pszBody = pszNextLine + 1;
1101 iLine += 1;
1102 while (*pszBody == '\n')
1103 {
1104 pszBody++;
1105 cchBody--;
1106 iLine++;
1107 cBlankLinesAfterCopyright++;
1108 }
1109 }
1110
1111 /*
1112 * Look for the license text.
1113 */
1114 if (pState->iLineLicense == UINT32_MAX)
1115 {
1116 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1117 {
1118 const char *pszNext;
1119 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1120 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1121 {
1122 while ( RT_C_IS_SPACE(*pszNext)
1123 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1124 pszNext++;
1125
1126 uint32_t cDashes = 0;
1127 while (*pszNext == '-')
1128 cDashes++, pszNext++;
1129 bool fExternal = cDashes > 10;
1130
1131 if ( *pszNext == '\0'
1132 || fExternal)
1133 {
1134 /* In C/C++ code, this must be a multiline comment. While in python it
1135 must be a */
1136 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1137 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1138 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1139 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1140
1141 /* Quit if we've flagged a failure. */
1142 if (RT_FAILURE(pState->pState->rc))
1143 return VERR_CALLBACK_RETURN;
1144
1145 /* Record it. */
1146 pState->iLineLicense = iLine;
1147 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody);
1148 pState->pCurrentLicense = pCur;
1149 pState->fExternalLicense = fExternal;
1150 pState->fIsCorrectLicense = pState->fOpenSource
1151 ? pCur == pState->pExpectedLicense
1152 : pCur->enmType == kScmLicenseType_Confidential;
1153 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1154 if (!pState->fWellFormedLicense)
1155 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1156
1157 /* If there was more than one blank line between the copyright and the
1158 license text, extend the license text area and force a rewrite of it. */
1159 if (cBlankLinesAfterCopyright > 1)
1160 {
1161 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1162 cBlankLinesAfterCopyright);
1163 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1164 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1165 pState->fWellFormedLicense = false;
1166 }
1167
1168 /* If there was more than one blank line after the license, trigger a rewrite. */
1169 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1170 {
1171 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1172 pInfo->cBlankLinesAfter);
1173 pState->fWellFormedLicense = false;
1174 }
1175
1176 /** @todo Check that the last comment line doesn't have any code on it. */
1177 /** @todo Check that column 2 contains '*' for C/C++ files. */
1178
1179 ScmVerbose(pState->pState, 2,
1180 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1181 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1182 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1183 pState->fExternalLicense, pState->fOpenSource);
1184
1185 if (fFoundCopyright)
1186 {
1187 pState->iLineComment = pInfo->iLineStart;
1188 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1189 - pInfo->iLineStart;
1190 }
1191 else
1192 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1193 if (pState->iLineCopyright != UINT32_MAX)
1194 return VERR_CALLBACK_RETURN;
1195 break;
1196 }
1197 }
1198 }
1199 }
1200
1201 if (fFoundCopyright)
1202 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1203 return VINF_SUCCESS;
1204}
1205
1206
1207/**
1208 * Updates the copyright year and/or license text.
1209 *
1210 * @returns true if modifications were made, false if not.
1211 * @param pState The rewriter state.
1212 * @param pIn The input stream.
1213 * @param pOut The output stream.
1214 * @param pSettings The settings.
1215 * @param enmCommentStyle The comment style used by the file.
1216 */
1217static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1218 SCMCOMMENTSTYLE enmCommentStyle)
1219{
1220 if ( !pSettings->fUpdateCopyrightYear
1221 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1222 return false;
1223
1224 /*
1225 * Try locate the relevant comments.
1226 */
1227 SCMCOPYRIGHTINFO Info =
1228 {
1229 /*.pState = */ pState,
1230 /*.enmCommentStyle = */ enmCommentStyle,
1231
1232 /*.pszContributedBy = */ NULL,
1233
1234 /*.iLineComment = */ UINT32_MAX,
1235 /*.cLinesComment = */ 0,
1236
1237 /*.iLineCopyright = */ UINT32_MAX,
1238 /*.uFirstYear = */ UINT32_MAX,
1239 /*.uLastYear = */ UINT32_MAX,
1240 /*.fWellFormedCopyright = */ false,
1241 /*.fUpToDateCopyright = */ false,
1242
1243 /*.fOpenSource = */ true,
1244 /*.pExpectedLicense = */ NULL,
1245 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1246 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1247 /*.iLineLicense = */ UINT32_MAX,
1248 /*.cLinesLicense = */ 0,
1249 /*.pCurrentLicense = */ NULL,
1250 /*.fIsCorrectLicense = */ false,
1251 /*.fWellFormedLicense = */ false,
1252 /*.fExternalLicense = */ false,
1253 };
1254
1255 /* Figure Info.fOpenSource and the desired license: */
1256 char *pszSyncProcess;
1257 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1258 if (RT_SUCCESS(rc))
1259 {
1260 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1261 RTStrFree(pszSyncProcess);
1262 }
1263 else if (rc == VERR_NOT_FOUND)
1264 Info.fOpenSource = false;
1265 else
1266 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1267
1268 Info.pExpectedLicense = Info.paLicenses;
1269 if (Info.fOpenSource)
1270 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1271 Info.pExpectedLicense++;
1272 else
1273 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1274 Info.pExpectedLicense++;
1275
1276 /* Scan the comments. */
1277 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1278 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1279 && RT_SUCCESS(pState->rc))
1280 {
1281 if (pSettings->fExternalCopyright)
1282 {
1283 if (Info.iLineCopyright != UINT32_MAX)
1284 ScmError(pState, VERR_NOT_FOUND,
1285 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1286 Info.iLineCopyright + 1);
1287 }
1288 else if (Info.iLineCopyright == UINT32_MAX)
1289 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1290 else if (Info.iLineLicense == UINT32_MAX)
1291 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1292 else
1293 {
1294 /*
1295 * Do we need to make any changes?
1296 */
1297 bool fUpdateCopyright = !Info.fWellFormedCopyright
1298 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear);
1299 bool fUpdateLicense = Info.enmLicenceOpt != kScmLicense_LeaveAlone
1300 && ( !Info.fWellFormedLicense
1301 || !Info.fIsCorrectLicense);
1302 if (fUpdateCopyright || fUpdateLicense)
1303 {
1304 Assert(Info.iLineComment != UINT32_MAX);
1305 Assert(Info.cLinesComment > 0);
1306
1307 /*
1308 * Okay, do the work.
1309 */
1310 ScmStreamRewindForReading(pIn);
1311
1312 if (pSettings->fUpdateCopyrightYear)
1313 Info.uLastYear = g_uYear;
1314
1315 uint32_t iLine = 0;
1316 SCMEOL enmEol;
1317 size_t cchLine;
1318 const char *pchLine;
1319 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1320 {
1321 if (iLine == Info.iLineComment)
1322 {
1323 /* Leading blank line. */
1324 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1325 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1326
1327 /* Contributed by someone? */
1328 if (Info.pszContributedBy)
1329 {
1330 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1331 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1332 ScmStreamWrite(pOut, Info.pszContributedBy, strlen(Info.pszContributedBy));
1333 ScmStreamPutEol(pOut, enmEol);
1334
1335 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1336 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1337 }
1338
1339 /* Write the copyright comment line. */
1340 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1341 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1342
1343 char szCopyright[256];
1344 size_t cchCopyright;
1345 if (Info.uFirstYear == Info.uLastYear)
1346 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1347 Info.uFirstYear, g_szCopyrightHolder);
1348 else
1349 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1350 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1351
1352 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1353 ScmStreamPutEol(pOut, enmEol);
1354
1355 /* Blank line separating the two. */
1356 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1357 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1358
1359 /* Write the license text. */
1360 Assert(Info.pExpectedLicense->psz[Info.pExpectedLicense->cch - 1] == '\n');
1361 Assert(Info.pExpectedLicense->psz[Info.pExpectedLicense->cch - 2] != '\n');
1362 const char *psz = Info.pExpectedLicense->psz;
1363 do
1364 {
1365 const char *pszEol = strchr(psz, '\n');
1366 if (pszEol != psz)
1367 {
1368 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1369 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1370 ScmStreamWrite(pOut, psz, pszEol - psz);
1371 ScmStreamPutEol(pOut, enmEol);
1372 }
1373 else
1374 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1375 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1376 psz = pszEol + 1;
1377 } while (*psz != '\0');
1378
1379 /* Final comment line (picking up the stream status here). */
1380 if (!Info.fExternalLicense)
1381 rc = ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1382 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1383 else
1384 rc = ScmStreamGetStatus(pOut);
1385
1386 /* Skip the copyright and license text in the input file. */
1387 if (RT_SUCCESS(rc))
1388 {
1389 iLine = Info.iLineComment + Info.cLinesComment;
1390 rc = ScmStreamSeekByLine(pIn, iLine);
1391 }
1392 }
1393 else
1394 {
1395 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1396 iLine++;
1397 }
1398 if (RT_FAILURE(rc))
1399 {
1400 RTStrFree(Info.pszContributedBy);
1401 return false;
1402 }
1403 } /* for each source line */
1404
1405 RTStrFree(Info.pszContributedBy);
1406 return true;
1407 }
1408 }
1409 }
1410 else
1411 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1412 NOREF(pState); NOREF(pOut);
1413 RTStrFree(Info.pszContributedBy);
1414 return false;
1415}
1416
1417
1418/** Copyright updater for C-style comments. */
1419bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1420{
1421 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
1422}
1423
1424/** Copyright updater for hash-prefixed comments. */
1425bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1426{
1427 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
1428}
1429
1430/** Copyright updater for REM-prefixed comments. */
1431bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1432{
1433 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
1434}
1435
1436/** Copyright updater for python comments. */
1437bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1438{
1439 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
1440}
1441
1442/** Copyright updater for semicolon-prefixed comments. */
1443bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1444{
1445 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
1446}
1447
1448
1449/**
1450 * Makefile.kup are empty files, enforce this.
1451 *
1452 * @returns true if modifications were made, false if not.
1453 * @param pIn The input stream.
1454 * @param pOut The output stream.
1455 * @param pSettings The settings.
1456 */
1457bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1458{
1459 RT_NOREF2(pOut, pSettings);
1460
1461 /* These files should be zero bytes. */
1462 if (pIn->cb == 0)
1463 return false;
1464 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
1465 return true;
1466}
1467
1468/**
1469 * Rewrite a kBuild makefile.
1470 *
1471 * @returns true if modifications were made, false if not.
1472 * @param pIn The input stream.
1473 * @param pOut The output stream.
1474 * @param pSettings The settings.
1475 *
1476 * @todo
1477 *
1478 * Ideas for Makefile.kmk and Config.kmk:
1479 * - sort if1of/ifn1of sets.
1480 * - line continuation slashes should only be preceded by one space.
1481 */
1482bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1483{
1484 RT_NOREF4(pState, pIn, pOut, pSettings);
1485 return false;
1486}
1487
1488
1489static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
1490 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
1491{
1492 *ppchText = NULL;
1493 *pcchText = 0;
1494 *pfNeedFixing = false;
1495
1496 /*
1497 * The first line.
1498 */
1499 if (pchLine[0] != '/')
1500 return false;
1501 size_t offLine = 1;
1502 while (offLine < cchLine && pchLine[offLine] == '*')
1503 offLine++;
1504 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
1505 return false;
1506 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
1507 offLine++;
1508 if (offLine != cchLine)
1509 return false;
1510
1511 size_t const cchBox = cchLine;
1512 *pfNeedFixing = cchBox != cchWidth;
1513
1514 /*
1515 * The next line, extracting the text.
1516 */
1517 SCMEOL enmEol;
1518 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
1519 if (cchLine < cchBox - 3)
1520 return false;
1521
1522 offLine = 0;
1523 if (RT_C_IS_BLANK(pchLine[0]))
1524 {
1525 *pfNeedFixing = true;
1526 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
1527 }
1528
1529 if (pchLine[offLine] != '*')
1530 return false;
1531 offLine++;
1532
1533 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
1534 return false;
1535 offLine++;
1536
1537 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
1538 offLine++;
1539 if (offLine >= cchLine)
1540 return false;
1541 if (!RT_C_IS_UPPER(pchLine[offLine]))
1542 return false;
1543
1544 if (offLine != 4 || cchLine != cchBox)
1545 *pfNeedFixing = true;
1546
1547 *ppchText = &pchLine[offLine];
1548 size_t const offText = offLine;
1549
1550 /* From the end now. */
1551 offLine = cchLine - 1;
1552 while (RT_C_IS_BLANK(pchLine[offLine]))
1553 offLine--;
1554
1555 if (pchLine[offLine] != '*')
1556 return false;
1557 offLine--;
1558 if (!RT_C_IS_BLANK(pchLine[offLine]))
1559 return false;
1560 offLine--;
1561 while (RT_C_IS_BLANK(pchLine[offLine]))
1562 offLine--;
1563 *pcchText = offLine - offText + 1;
1564
1565 /*
1566 * Third line closes the box.
1567 */
1568 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
1569 if (cchLine < cchBox - 3)
1570 return false;
1571
1572 offLine = 0;
1573 if (RT_C_IS_BLANK(pchLine[0]))
1574 {
1575 *pfNeedFixing = true;
1576 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
1577 }
1578 while (offLine < cchLine && pchLine[offLine] == '*')
1579 offLine++;
1580 if (offLine < cchBox - 4)
1581 return false;
1582
1583 if (pchLine[offLine] != '/')
1584 return false;
1585 offLine++;
1586
1587 if (offLine != cchBox)
1588 *pfNeedFixing = true;
1589
1590 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
1591 offLine++;
1592 if (offLine != cchLine)
1593 return false;
1594
1595 return true;
1596}
1597
1598
1599/**
1600 * Flower box marker comments in C and C++ code.
1601 *
1602 * @returns true if modifications were made, false if not.
1603 * @param pIn The input stream.
1604 * @param pOut The output stream.
1605 * @param pSettings The settings.
1606 */
1607bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1608{
1609 if (!pSettings->fFixFlowerBoxMarkers)
1610 return false;
1611
1612 /*
1613 * Work thru the file line by line looking for flower box markers.
1614 */
1615 size_t cChanges = 0;
1616 size_t cBlankLines = 0;
1617 SCMEOL enmEol;
1618 size_t cchLine;
1619 const char *pchLine;
1620 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1621 {
1622 /*
1623 * Get a likely match for a first line.
1624 */
1625 if ( pchLine[0] == '/'
1626 && cchLine > 20
1627 && pchLine[1] == '*'
1628 && pchLine[2] == '*'
1629 && pchLine[3] == '*')
1630 {
1631 size_t const offSaved = ScmStreamTell(pIn);
1632 char const *pchText;
1633 size_t cchText;
1634 bool fNeedFixing;
1635 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
1636 &pchText, &cchText, &fNeedFixing);
1637 if ( fIsFlowerBoxSection
1638 && ( fNeedFixing
1639 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
1640 {
1641 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
1642 {
1643 ScmStreamPutEol(pOut, enmEol);
1644 cBlankLines++;
1645 }
1646
1647 ScmStreamPutCh(pOut, '/');
1648 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
1649 ScmStreamPutEol(pOut, enmEol);
1650
1651 static const char s_szLead[] = "* ";
1652 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
1653 ScmStreamWrite(pOut, pchText, cchText);
1654 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
1655 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
1656 ScmStreamPutCh(pOut, '*');
1657 ScmStreamPutEol(pOut, enmEol);
1658
1659 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
1660 ScmStreamPutCh(pOut, '/');
1661 ScmStreamPutEol(pOut, enmEol);
1662
1663 cChanges++;
1664 cBlankLines = 0;
1665 continue;
1666 }
1667
1668 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
1669 if (RT_FAILURE(rc))
1670 return false;
1671 }
1672
1673 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1674 if (RT_FAILURE(rc))
1675 return false;
1676
1677 /* Do blank line accounting so we can ensure at least two blank lines
1678 before each section marker. */
1679 if (!isBlankLine(pchLine, cchLine))
1680 cBlankLines = 0;
1681 else
1682 cBlankLines++;
1683 }
1684 if (cChanges > 0)
1685 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
1686 return cChanges != 0;
1687}
1688
1689
1690/**
1691 * Looks for the start of a todo comment.
1692 *
1693 * @returns Offset into the line of the comment start sequence.
1694 * @param pchLine The line to search.
1695 * @param cchLineBeforeTodo The length of the line before the todo.
1696 * @param pfSameLine Indicates whether it's refering to a statemtn on
1697 * the same line comment (true), or the next
1698 * statement (false).
1699 */
1700static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
1701{
1702 *pfSameLine = false;
1703
1704 /* Skip one '@' or '\\'. */
1705 char ch;
1706 if ( cchLineBeforeTodo > 2
1707 && ( (ch = pchLine[cchLineBeforeTodo - 1] == '@')
1708 || ch == '\\' ) )
1709 cchLineBeforeTodo--;
1710
1711 /* Skip blanks. */
1712 while ( cchLineBeforeTodo > 2
1713 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
1714 cchLineBeforeTodo--;
1715
1716 /* Look for same line indicator. */
1717 if ( cchLineBeforeTodo > 0
1718 && pchLine[cchLineBeforeTodo - 1] == '<')
1719 {
1720 *pfSameLine = true;
1721 cchLineBeforeTodo--;
1722 }
1723
1724 /* Skip *s */
1725 while ( cchLineBeforeTodo > 1
1726 && pchLine[cchLineBeforeTodo - 1] == '*')
1727 cchLineBeforeTodo--;
1728
1729 /* Do we have a comment opening sequence. */
1730 if ( cchLineBeforeTodo > 0
1731 && pchLine[cchLineBeforeTodo - 1] == '/'
1732 && ( ( cchLineBeforeTodo >= 2
1733 && pchLine[cchLineBeforeTodo - 2] == '/')
1734 || pchLine[cchLineBeforeTodo] == '*'))
1735 {
1736 /* Skip slashes at the start. */
1737 while ( cchLineBeforeTodo > 0
1738 && pchLine[cchLineBeforeTodo - 1] == '/')
1739 cchLineBeforeTodo--;
1740
1741 return cchLineBeforeTodo;
1742 }
1743
1744 return ~(size_t)0;
1745}
1746
1747
1748/**
1749 * Looks for a TODO or todo in the given line.
1750 *
1751 * @returns Offset into the line of found, ~(size_t)0 if not.
1752 * @param pchLine The line to search.
1753 * @param cchLine The length of the line.
1754 */
1755static size_t findTodo(char const *pchLine, size_t cchLine)
1756{
1757 if (cchLine >= 4 + 2)
1758 {
1759 /* We don't search the first to chars because we need the start of a comment.
1760 Also, skip the last three chars since we need at least four for a match. */
1761 size_t const cchLineT = cchLine - 3;
1762 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
1763 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
1764 {
1765 for (size_t off = 2; off < cchLineT; off++)
1766 {
1767 char ch = pchLine[off];
1768 if ( ( ch != 't'
1769 && ch != 'T')
1770 || ( (ch = pchLine[off + 1]) != 'o'
1771 && ch != 'O')
1772 || ( (ch = pchLine[off + 2]) != 'd'
1773 && ch != 'D')
1774 || ( (ch = pchLine[off + 3]) != 'o'
1775 && ch != 'O')
1776 || ( off + 4 != cchLine
1777 && (ch = pchLine[off + 4]) != ' '
1778 && ch != '\t'
1779 && ch != ':' /** @todo */
1780 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
1781 ) )
1782 { /* not a hit - likely */ }
1783 else
1784 return off;
1785 }
1786 }
1787 }
1788 return ~(size_t)0;
1789}
1790
1791
1792/**
1793 * Flower box marker comments in C and C++ code.
1794 *
1795 * @returns true if modifications were made, false if not.
1796 * @param pIn The input stream.
1797 * @param pOut The output stream.
1798 * @param pSettings The settings.
1799 */
1800bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1801{
1802 if (!pSettings->fFixTodos)
1803 return false;
1804
1805 /*
1806 * Work thru the file line by line looking for the start of todo comments.
1807 */
1808 size_t cChanges = 0;
1809 SCMEOL enmEol;
1810 size_t cchLine;
1811 const char *pchLine;
1812 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1813 {
1814 /*
1815 * Look for the word 'todo' in the line. We're currently only trying
1816 * to catch comments starting with the word todo and adjust the start of
1817 * the doxygen statement.
1818 */
1819 size_t offTodo = findTodo(pchLine, cchLine);
1820 if ( offTodo != ~(size_t)0
1821 && offTodo >= 2)
1822 {
1823 /* Work backwards to find the start of the comment. */
1824 bool fSameLine = false;
1825 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
1826 if (offCommentStart != ~(size_t)0)
1827 {
1828 char szNew[64];
1829 size_t cchNew = 0;
1830 szNew[cchNew++] = '/';
1831 szNew[cchNew++] = pchLine[offCommentStart + 1];
1832 szNew[cchNew++] = pchLine[offCommentStart + 1];
1833 if (fSameLine)
1834 szNew[cchNew++] = '<';
1835 szNew[cchNew++] = ' ';
1836 szNew[cchNew++] = '@';
1837 szNew[cchNew++] = 't';
1838 szNew[cchNew++] = 'o';
1839 szNew[cchNew++] = 'd';
1840 szNew[cchNew++] = 'o';
1841
1842 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
1843 but need to take into account that we might be at the end of the line before
1844 adding the space. */
1845 size_t offTodoAfter = offTodo + 4;
1846 if ( offTodoAfter < cchLine
1847 && pchLine[offTodoAfter] == ':')
1848 offTodoAfter++;
1849 if ( offTodoAfter < cchLine
1850 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
1851 offTodoAfter++;
1852 if (offTodoAfter < cchLine)
1853 szNew[cchNew++] = ' ';
1854
1855 /* Write it out. */
1856 ScmStreamWrite(pOut, pchLine, offCommentStart);
1857 ScmStreamWrite(pOut, szNew, cchNew);
1858 if (offTodoAfter < cchLine)
1859 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
1860 ScmStreamPutEol(pOut, enmEol);
1861
1862 /* Check whether we actually made any changes. */
1863 if ( cchNew != offTodoAfter - offCommentStart
1864 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
1865 cChanges++;
1866 continue;
1867 }
1868 }
1869
1870 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1871 if (RT_FAILURE(rc))
1872 return false;
1873 }
1874 if (cChanges > 0)
1875 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
1876 return cChanges != 0;
1877}
1878
1879
1880/**
1881 * Rewrite a C/C++ source or header file.
1882 *
1883 * @returns true if modifications were made, false if not.
1884 * @param pIn The input stream.
1885 * @param pOut The output stream.
1886 * @param pSettings The settings.
1887 *
1888 * @todo
1889 *
1890 * Ideas for C/C++:
1891 * - space after if, while, for, switch
1892 * - spaces in for (i=0;i<x;i++)
1893 * - complex conditional, bird style.
1894 * - remove unnecessary parentheses.
1895 * - sort defined RT_OS_*|| and RT_ARCH
1896 * - sizeof without parenthesis.
1897 * - defined without parenthesis.
1898 * - trailing spaces.
1899 * - parameter indentation.
1900 * - space after comma.
1901 * - while (x--); -> multi line + comment.
1902 * - else statement;
1903 * - space between function and left parenthesis.
1904 * - TODO, XXX, @todo cleanup.
1905 * - Space before/after '*'.
1906 * - ensure new line at end of file.
1907 * - Indentation of precompiler statements (#ifdef, #defines).
1908 * - space between functions.
1909 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
1910 */
1911bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1912{
1913
1914 RT_NOREF4(pState, pIn, pOut, pSettings);
1915 return false;
1916}
1917
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