VirtualBox

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

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

scm: switched back to the first MIT license wording. Sample code is using it so we should use it everywhere.

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