VirtualBox

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

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

scm: License and copyright updating. [build fix]

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