VirtualBox

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

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

scm: Multiline contrib stuff. DTrace scripts. Darwin icons.

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