VirtualBox

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

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

scm: fix PNG svn properties

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