VirtualBox

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

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

scm: Fixed incorrect comment start column when parsing python doc strings. Display why we don't find license and copyright stuff not well formed and need to redo them.

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

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