VirtualBox

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

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

scm: License and copyright updating.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 63.3 KB
Line 
1/* $Id: scmrw.cpp 69166 2017-10-23 15:43:33Z 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 return true;
751 }
752 else
753 {
754 /* Try skip spaces an punctuation. */
755 while ( RT_C_IS_SPACE(ch1)
756 || RT_C_IS_PUNCT(ch1))
757 ch1 = *++psz1;
758
759 if (ch1 == '\0' && ppsz2Next)
760 {
761 *ppsz2Next = psz2;
762 return true;
763 }
764
765 while ( RT_C_IS_SPACE(ch2)
766 || RT_C_IS_PUNCT(ch2))
767 ch2 = *++psz2;
768
769 if ( ch1 != ch2
770 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
771 return false;
772 }
773 }
774}
775
776
777/**
778 * Counts the number of lines in the given substring.
779 *
780 * @returns The number of lines.
781 * @param psz The start of the substring.
782 * @param cch The length of the substring.
783 */
784static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
785{
786 uint32_t cLines = 0;
787 for (;;)
788 {
789 const char *pszEol = (const char *)memchr(psz, '\n', cch);
790 if (pszEol)
791 cLines++;
792 else
793 return cLines + (*psz != '\0');
794 cch -= pszEol + 1 - psz;
795 if (!cch)
796 return cLines;
797 psz = pszEol + 1;
798 }
799}
800
801
802/**
803 * Comment parser callback for locating copyright and license.
804 */
805static DECLCALLBACK(int)
806rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
807{
808 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
809 Assert(strlen(pszBody) == cchBody);
810 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
811 ScmVerbose(pState->pState, 2,
812 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
813 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
814 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
815
816 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
817
818 /*
819 * Since the license statement is typically prefaced by a copyright line.
820 */
821 bool fFoundCopyright = false;
822 uint32_t cBlankLinesAfterCopyright = 0;
823 if ( pState->iLineCopyright == UINT32_MAX
824 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
825 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
826 {
827 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
828
829 /* Oracle copyright? */
830 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
831 while (RT_C_IS_SPACE(pszEnd[-1]))
832 pszEnd--;
833 if ( (pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
834 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
835 {
836 /* Parse out the year(s). */
837 const char *psz = pszBody + sizeof("copyright");
838 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
839 psz++;
840 if (RT_C_IS_DIGIT(*psz))
841 {
842 char *pszNext;
843 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
844 if ( RT_SUCCESS(rc)
845 && rc != VWRN_NUMBER_TOO_BIG
846 && rc != VWRN_NEGATIVE_UNSIGNED)
847 {
848 if ( pState->uFirstYear < 1975
849 || pState->uFirstYear > 3000)
850 {
851 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
852 pState->uFirstYear, pszEnd - pszBody, pszBody);
853 pState->uFirstYear = UINT32_MAX;
854 }
855
856 while (RT_C_IS_SPACE(*pszNext))
857 pszNext++;
858 if (*pszNext == '-')
859 {
860 do
861 pszNext++;
862 while (RT_C_IS_SPACE(*pszNext));
863 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
864 if ( RT_SUCCESS(rc)
865 && rc != VWRN_NUMBER_TOO_BIG
866 && rc != VWRN_NEGATIVE_UNSIGNED)
867 {
868 if ( pState->uLastYear < 1975
869 || pState->uLastYear > 3000)
870 {
871 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
872 pState->uLastYear, pszEnd - pszBody, pszBody);
873 pState->uLastYear = UINT32_MAX;
874 }
875 else if (pState->uFirstYear > pState->uLastYear)
876 {
877 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
878 uint32_t iTmp = pState->uLastYear;
879 pState->uLastYear = pState->uFirstYear;
880 pState->uFirstYear = iTmp;
881 }
882 }
883 else
884 {
885 pState->uLastYear = UINT32_MAX;
886 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
887 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
888 }
889 }
890 else if (*pszNext != g_szCopyrightHolder[0])
891 ScmError(pState->pState, VERR_PARSE_ERROR,
892 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
893 else
894 pState->uLastYear = pState->uFirstYear;
895 }
896 else
897 {
898 pState->uFirstYear = UINT32_MAX;
899 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
900 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
901 }
902 }
903
904 /* The copyright comment must come before the license. */
905 if (pState->iLineLicense != UINT32_MAX)
906 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
907 iLine, pState->iLineLicense);
908
909 /* In C/C++ code, this must be a multiline comment. While in python it
910 must be a */
911 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
912 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
913 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
914 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
915
916 /* The copyright must be followed by the license. */
917 if (!pszNextLine)
918 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
919
920 /* Quit if we've flagged a failure. */
921 if (RT_FAILURE(pState->pState->rc))
922 return VERR_CALLBACK_RETURN;
923
924 /* Check if it's well formed and up to date. */
925 char szWellFormed[256];
926 size_t cchWellFormed;
927 if (pState->uFirstYear == pState->uLastYear)
928 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
929 pState->uFirstYear, g_szCopyrightHolder);
930 else
931 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
932 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
933 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
934 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
935 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
936 pState->iLineCopyright = iLine;
937
938 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
939 if (pInfo->cBlankLinesBefore != 1)
940 pState->fWellFormedCopyright = false;
941
942 /* If the comment doesn't start in column 1, trigger rewrite. */
943 if (pInfo->offStart != 0)
944 {
945 pState->fWellFormedCopyright = false;
946 /** @todo check that there isn't any code preceeding the comment. */
947 }
948
949 fFoundCopyright = true;
950 ScmVerbose(pState->pState, 2, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
951 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
952 }
953 else
954 ScmVerbose(pState->pState, 2, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
955
956 if (!pszNextLine)
957 return VINF_SUCCESS;
958
959 /* Skip the copyright line and any blank lines following it. */
960 cchBody -= pszNextLine - pszBody + 1;
961 pszBody = pszNextLine + 1;
962 iLine += 1;
963 while (*pszBody == '\n')
964 {
965 pszBody++;
966 cchBody--;
967 iLine++;
968 cBlankLinesAfterCopyright++;
969 }
970 }
971
972 /*
973 * Now try match against the license texts if we haven't already found it.
974 */
975 if (pState->iLineLicense == UINT32_MAX)
976 {
977 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
978 {
979 const char *pszNext;
980 if ( pCur->cch - 1 <= cchBody
981 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
982 {
983 while ( RT_C_IS_SPACE(*pszNext)
984 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
985 pszNext++;
986
987 uint32_t cDashes = 0;
988 while (*pszNext == '-')
989 cDashes++, pszNext++;
990 bool fExternal = cDashes > 10;
991
992 if ( *pszNext == '\0'
993 || fExternal)
994 {
995 /* In C/C++ code, this must be a multiline comment. While in python it
996 must be a */
997 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
998 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
999 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1000 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1001
1002 /* Quit if we've flagged a failure. */
1003 if (RT_FAILURE(pState->pState->rc))
1004 return VERR_CALLBACK_RETURN;
1005
1006 /* Record it. */
1007 pState->iLineLicense = iLine;
1008 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody);
1009 pState->pCurrentLicense = pCur;
1010 pState->fExternalLicense = fExternal;
1011 pState->fIsCorrectLicense = pState->fOpenSource
1012 ? pCur->enmOpt == pState->enmLicenceOpt
1013 : pCur->enmType == kScmLicenseType_Confidential;
1014 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1015
1016 /* If there was more than one blank line between the copyright and the
1017 license text, extend the license text area and force a rewrite of it. */
1018 if (cBlankLinesAfterCopyright > 1)
1019 {
1020 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1021 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1022 pState->fWellFormedLicense = false;
1023 }
1024
1025 /* If there was more than one blank line after the license, trigger a rewrite. */
1026 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1027 pState->fWellFormedLicense = false;
1028
1029 /** @todo Check that the last comment line doesn't have any code on it. */
1030 /** @todo Check that column 2 contains '*' for C/C++ files. */
1031
1032 ScmVerbose(pState->pState, 2,
1033 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1034 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1035 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1036 pState->fExternalLicense, pState->fOpenSource);
1037
1038 if (fFoundCopyright)
1039 {
1040 pState->iLineComment = pInfo->iLineStart;
1041 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1042 - pInfo->iLineStart;
1043 }
1044 else
1045 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1046 if (pState->iLineCopyright != UINT32_MAX)
1047 return VERR_CALLBACK_RETURN;
1048 break;
1049 }
1050 }
1051 }
1052 }
1053
1054 if (fFoundCopyright)
1055 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1056 return VINF_SUCCESS;
1057}
1058
1059
1060/**
1061 * Updates the copyright year and/or license text.
1062 *
1063 * @returns true if modifications were made, false if not.
1064 * @param pState The rewriter state.
1065 * @param pIn The input stream.
1066 * @param pOut The output stream.
1067 * @param pSettings The settings.
1068 * @param enmCommentStyle The comment style used by the file.
1069 */
1070static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1071 SCMCOMMENTSTYLE enmCommentStyle)
1072{
1073 if ( !pSettings->fUpdateCopyrightYear
1074 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1075 return false;
1076
1077 /*
1078 * Try locate the relevant comments.
1079 */
1080 SCMCOPYRIGHTINFO Info =
1081 {
1082 /*.pState = */ pState,
1083 /*.enmCommentStyle = */ enmCommentStyle,
1084
1085 /*.iLineComment = */ UINT32_MAX,
1086 /*.cLinesComment = */ 0,
1087
1088 /*.iLineCopyright = */ UINT32_MAX,
1089 /*.uFirstYear = */ UINT32_MAX,
1090 /*.uLastYear = */ UINT32_MAX,
1091 /*.fWellFormedCopyright = */ false,
1092 /*.fUpToDateCopyright = */ false,
1093
1094 /*.fOpenSource = */ true,
1095 /*.pExpectedLicense = */ NULL,
1096 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1097 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1098 /*.iLineLicense = */ UINT32_MAX,
1099 /*.cLinesLicense = */ 0,
1100 /*.pCurrentLicense = */ NULL,
1101 /*.fIsCorrectLicense = */ false,
1102 /*.fWellFormedLicense = */ false,
1103 /*.fExternalLicense = */ false,
1104 };
1105
1106 /* Figure Info.fOpenSource and the desired license: */
1107 char *pszSyncProcess;
1108 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1109 if (RT_SUCCESS(rc))
1110 {
1111 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1112 RTStrFree(pszSyncProcess);
1113 }
1114 else if (rc == VERR_NOT_FOUND)
1115 Info.fOpenSource = false;
1116 else
1117 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1118
1119 Info.pExpectedLicense = Info.paLicenses;
1120 if (Info.fOpenSource)
1121 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1122 Info.pExpectedLicense++;
1123 else
1124 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1125 Info.pExpectedLicense++;
1126
1127 /* Scan the comments. */
1128 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1129 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1130 && RT_SUCCESS(pState->rc))
1131 {
1132 if (Info.iLineCopyright == UINT32_MAX)
1133 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1134 else if (Info.iLineLicense == UINT32_MAX)
1135 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1136 else
1137 {
1138 /*
1139 * Do we need to make any changes?
1140 */
1141 bool fUpdateCopyright = !Info.fWellFormedCopyright
1142 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear);
1143 bool fUpdateLicense = Info.enmLicenceOpt != kScmLicense_LeaveAlone
1144 && ( !Info.fWellFormedLicense
1145 || !Info.fIsCorrectLicense);
1146 if (fUpdateCopyright || fUpdateLicense)
1147 {
1148 Assert(Info.iLineComment != UINT32_MAX);
1149 Assert(Info.cLinesComment > 0);
1150
1151 /*
1152 * Okay, do the work.
1153 */
1154 ScmStreamRewindForReading(pIn);
1155
1156 if (pSettings->fUpdateCopyrightYear)
1157 Info.uLastYear = g_uYear;
1158
1159 uint32_t iLine = 0;
1160 SCMEOL enmEol;
1161 size_t cchLine;
1162 const char *pchLine;
1163 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1164 {
1165 if (iLine == Info.iLineComment)
1166 {
1167 /* Leading blank line. */
1168 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1169 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1170
1171 /* Write the copyright comment line. */
1172 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1173 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1174
1175 char szCopyright[256];
1176 size_t cchCopyright;
1177 if (Info.uFirstYear == Info.uLastYear)
1178 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1179 Info.uFirstYear, g_szCopyrightHolder);
1180 else
1181 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1182 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1183
1184 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1185 ScmStreamPutEol(pOut, enmEol);
1186
1187 /* Blank line separating the two. */
1188 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1189 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1190
1191 /* Write the license text. */
1192 Assert(Info.pExpectedLicense->psz[Info.pExpectedLicense->cch - 1] == '\n');
1193 Assert(Info.pExpectedLicense->psz[Info.pExpectedLicense->cch - 2] != '\n');
1194 const char *psz = Info.pExpectedLicense->psz;
1195 do
1196 {
1197 const char *pszEol = strchr(psz, '\n');
1198 if (pszEol != psz)
1199 {
1200 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1201 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1202 ScmStreamWrite(pOut, psz, pszEol - psz);
1203 ScmStreamPutEol(pOut, enmEol);
1204 }
1205 else
1206 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1207 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1208 psz = pszEol + 1;
1209 } while (*psz != '\0');
1210
1211 /* Final comment line (picking up the stream status here). */
1212 if (!Info.fExternalLicense)
1213 rc = ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1214 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1215 else
1216 rc = ScmStreamGetStatus(pOut);
1217
1218 /* Skip the copyright and license text in the input file. */
1219 if (RT_SUCCESS(rc))
1220 {
1221 iLine = Info.iLineComment + Info.cLinesComment;
1222 rc = ScmStreamSeekByLine(pIn, iLine);
1223 }
1224 }
1225 else
1226 {
1227 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1228 iLine++;
1229 }
1230 if (RT_FAILURE(rc))
1231 return false;
1232 }
1233 return true;
1234 }
1235 }
1236 }
1237 else
1238 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1239 NOREF(pState); NOREF(pOut);
1240 return false;
1241}
1242
1243
1244/** Copyright updater for C-style comments. */
1245bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1246{
1247 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
1248}
1249
1250/** Copyright updater for hash-prefixed comments. */
1251bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1252{
1253 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
1254}
1255
1256/** Copyright updater for REM-prefixed comments. */
1257bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1258{
1259 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
1260}
1261
1262/** Copyright updater for python comments. */
1263bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1264{
1265 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
1266}
1267
1268/** Copyright updater for semicolon-prefixed comments. */
1269bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1270{
1271 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
1272}
1273
1274
1275/**
1276 * Makefile.kup are empty files, enforce this.
1277 *
1278 * @returns true if modifications were made, false if not.
1279 * @param pIn The input stream.
1280 * @param pOut The output stream.
1281 * @param pSettings The settings.
1282 */
1283bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1284{
1285 RT_NOREF2(pOut, pSettings);
1286
1287 /* These files should be zero bytes. */
1288 if (pIn->cb == 0)
1289 return false;
1290 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
1291 return true;
1292}
1293
1294/**
1295 * Rewrite a kBuild makefile.
1296 *
1297 * @returns true if modifications were made, false if not.
1298 * @param pIn The input stream.
1299 * @param pOut The output stream.
1300 * @param pSettings The settings.
1301 *
1302 * @todo
1303 *
1304 * Ideas for Makefile.kmk and Config.kmk:
1305 * - sort if1of/ifn1of sets.
1306 * - line continuation slashes should only be preceded by one space.
1307 */
1308bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1309{
1310 RT_NOREF4(pState, pIn, pOut, pSettings);
1311 return false;
1312}
1313
1314
1315static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine,
1316 const char **ppchText, size_t *pcchText)
1317{
1318 *ppchText = NULL;
1319 *pcchText = 0;
1320
1321 /*
1322 * The first line.
1323 */
1324 if (pchLine[0] != '/')
1325 return false;
1326 size_t offLine = 1;
1327 while (offLine < cchLine && pchLine[offLine] == '*')
1328 offLine++;
1329 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
1330 return false;
1331 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
1332 offLine++;
1333 if (offLine != cchLine)
1334 return false;
1335
1336 size_t const cchBox = cchLine;
1337
1338 /*
1339 * The next line, extracting the text.
1340 */
1341 SCMEOL enmEol;
1342 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
1343 if (cchLine < cchBox - 3)
1344 return false;
1345
1346 offLine = 0;
1347 if (RT_C_IS_BLANK(pchLine[0]))
1348 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
1349
1350 if (pchLine[offLine] != '*')
1351 return false;
1352 offLine++;
1353
1354 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
1355 return false;
1356 offLine++;
1357
1358 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
1359 offLine++;
1360 if (offLine >= cchLine)
1361 return false;
1362 if (!RT_C_IS_UPPER(pchLine[offLine]))
1363 return false;
1364
1365 *ppchText = &pchLine[offLine];
1366 size_t const offText = offLine;
1367
1368 /* From the end now. */
1369 offLine = cchLine - 1;
1370 while (RT_C_IS_BLANK(pchLine[offLine]))
1371 offLine--;
1372
1373 if (pchLine[offLine] != '*')
1374 return false;
1375 offLine--;
1376 if (!RT_C_IS_BLANK(pchLine[offLine]))
1377 return false;
1378 offLine--;
1379 while (RT_C_IS_BLANK(pchLine[offLine]))
1380 offLine--;
1381 *pcchText = offLine - offText + 1;
1382
1383 /*
1384 * Third line closes the box.
1385 */
1386 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
1387 if (cchLine < cchBox - 3)
1388 return false;
1389
1390 offLine = 0;
1391 if (RT_C_IS_BLANK(pchLine[0]))
1392 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
1393 while (offLine < cchLine && pchLine[offLine] == '*')
1394 offLine++;
1395 if (offLine < cchBox - 4)
1396 return false;
1397
1398 if (pchLine[offLine] != '/')
1399 return false;
1400 offLine++;
1401
1402 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
1403 offLine++;
1404 if (offLine != cchLine)
1405 return false;
1406
1407 return true;
1408}
1409
1410
1411/**
1412 * Flower box marker comments in C and C++ code.
1413 *
1414 * @returns true if modifications were made, false if not.
1415 * @param pIn The input stream.
1416 * @param pOut The output stream.
1417 * @param pSettings The settings.
1418 */
1419bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1420{
1421 if (!pSettings->fFixFlowerBoxMarkers)
1422 return false;
1423
1424 /*
1425 * Work thru the file line by line looking for flower box markers.
1426 */
1427 size_t cChanges = 0;
1428 size_t cBlankLines = 0;
1429 SCMEOL enmEol;
1430 size_t cchLine;
1431 const char *pchLine;
1432 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1433 {
1434 /*
1435 * Get a likely match for a first line.
1436 */
1437 if ( pchLine[0] == '/'
1438 && cchLine > 20
1439 && pchLine[1] == '*'
1440 && pchLine[2] == '*'
1441 && pchLine[3] == '*')
1442 {
1443 size_t const offSaved = ScmStreamTell(pIn);
1444 char const *pchText;
1445 size_t cchText;
1446 if (isFlowerBoxSectionMarker(pIn, pchLine, cchLine, &pchText, &cchText))
1447 {
1448 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
1449 {
1450 ScmStreamPutEol(pOut, enmEol);
1451 cBlankLines++;
1452 }
1453
1454 ScmStreamPutCh(pOut, '/');
1455 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
1456 ScmStreamPutEol(pOut, enmEol);
1457
1458 static const char s_szLead[] = "* ";
1459 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
1460 ScmStreamWrite(pOut, pchText, cchText);
1461 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
1462 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
1463 ScmStreamPutCh(pOut, '*');
1464 ScmStreamPutEol(pOut, enmEol);
1465
1466 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
1467 ScmStreamPutCh(pOut, '/');
1468 ScmStreamPutEol(pOut, enmEol);
1469
1470 cChanges++;
1471 cBlankLines = 0;
1472 continue;
1473 }
1474
1475 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
1476 if (RT_FAILURE(rc))
1477 return false;
1478 }
1479
1480 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1481 if (RT_FAILURE(rc))
1482 return false;
1483
1484 /* Do blank line accounting so we can ensure at least two blank lines
1485 before each section marker. */
1486 if (!isBlankLine(pchLine, cchLine))
1487 cBlankLines = 0;
1488 else
1489 cBlankLines++;
1490 }
1491 if (cChanges > 0)
1492 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
1493 return cChanges != 0;
1494}
1495
1496
1497/**
1498 * Looks for the start of a todo comment.
1499 *
1500 * @returns Offset into the line of the comment start sequence.
1501 * @param pchLine The line to search.
1502 * @param cchLineBeforeTodo The length of the line before the todo.
1503 * @param pfSameLine Indicates whether it's refering to a statemtn on
1504 * the same line comment (true), or the next
1505 * statement (false).
1506 */
1507static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
1508{
1509 *pfSameLine = false;
1510
1511 /* Skip one '@' or '\\'. */
1512 char ch;
1513 if ( cchLineBeforeTodo > 2
1514 && ( (ch = pchLine[cchLineBeforeTodo - 1] == '@')
1515 || ch == '\\' ) )
1516 cchLineBeforeTodo--;
1517
1518 /* Skip blanks. */
1519 while ( cchLineBeforeTodo > 2
1520 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
1521 cchLineBeforeTodo--;
1522
1523 /* Look for same line indicator. */
1524 if ( cchLineBeforeTodo > 0
1525 && pchLine[cchLineBeforeTodo - 1] == '<')
1526 {
1527 *pfSameLine = true;
1528 cchLineBeforeTodo--;
1529 }
1530
1531 /* Skip *s */
1532 while ( cchLineBeforeTodo > 1
1533 && pchLine[cchLineBeforeTodo - 1] == '*')
1534 cchLineBeforeTodo--;
1535
1536 /* Do we have a comment opening sequence. */
1537 if ( cchLineBeforeTodo > 0
1538 && pchLine[cchLineBeforeTodo - 1] == '/'
1539 && ( ( cchLineBeforeTodo >= 2
1540 && pchLine[cchLineBeforeTodo - 2] == '/')
1541 || pchLine[cchLineBeforeTodo] == '*'))
1542 {
1543 /* Skip slashes at the start. */
1544 while ( cchLineBeforeTodo > 0
1545 && pchLine[cchLineBeforeTodo - 1] == '/')
1546 cchLineBeforeTodo--;
1547
1548 return cchLineBeforeTodo;
1549 }
1550
1551 return ~(size_t)0;
1552}
1553
1554
1555/**
1556 * Looks for a TODO or todo in the given line.
1557 *
1558 * @returns Offset into the line of found, ~(size_t)0 if not.
1559 * @param pchLine The line to search.
1560 * @param cchLine The length of the line.
1561 */
1562static size_t findTodo(char const *pchLine, size_t cchLine)
1563{
1564 if (cchLine >= 4 + 2)
1565 {
1566 /* We don't search the first to chars because we need the start of a comment.
1567 Also, skip the last three chars since we need at least four for a match. */
1568 size_t const cchLineT = cchLine - 3;
1569 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
1570 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
1571 {
1572 for (size_t off = 2; off < cchLineT; off++)
1573 {
1574 char ch = pchLine[off];
1575 if ( ( ch != 't'
1576 && ch != 'T')
1577 || ( (ch = pchLine[off + 1]) != 'o'
1578 && ch != 'O')
1579 || ( (ch = pchLine[off + 2]) != 'd'
1580 && ch != 'D')
1581 || ( (ch = pchLine[off + 3]) != 'o'
1582 && ch != 'O')
1583 || ( off + 4 != cchLine
1584 && (ch = pchLine[off + 4]) != ' '
1585 && ch != '\t'
1586 && ch != ':' /** @todo */
1587 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
1588 ) )
1589 { /* not a hit - likely */ }
1590 else
1591 return off;
1592 }
1593 }
1594 }
1595 return ~(size_t)0;
1596}
1597
1598
1599/**
1600 * Flower box marker comments in C and C++ code.
1601 *
1602 * @returns true if modifications were made, false if not.
1603 * @param pIn The input stream.
1604 * @param pOut The output stream.
1605 * @param pSettings The settings.
1606 */
1607bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1608{
1609 if (!pSettings->fFixTodos)
1610 return false;
1611
1612 /*
1613 * Work thru the file line by line looking for the start of todo comments.
1614 */
1615 size_t cChanges = 0;
1616 SCMEOL enmEol;
1617 size_t cchLine;
1618 const char *pchLine;
1619 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1620 {
1621 /*
1622 * Look for the word 'todo' in the line. We're currently only trying
1623 * to catch comments starting with the word todo and adjust the start of
1624 * the doxygen statement.
1625 */
1626 size_t offTodo = findTodo(pchLine, cchLine);
1627 if ( offTodo != ~(size_t)0
1628 && offTodo >= 2)
1629 {
1630 /* Work backwards to find the start of the comment. */
1631 bool fSameLine = false;
1632 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
1633 if (offCommentStart != ~(size_t)0)
1634 {
1635 char szNew[64];
1636 size_t cchNew = 0;
1637 szNew[cchNew++] = '/';
1638 szNew[cchNew++] = pchLine[offCommentStart + 1];
1639 szNew[cchNew++] = pchLine[offCommentStart + 1];
1640 if (fSameLine)
1641 szNew[cchNew++] = '<';
1642 szNew[cchNew++] = ' ';
1643 szNew[cchNew++] = '@';
1644 szNew[cchNew++] = 't';
1645 szNew[cchNew++] = 'o';
1646 szNew[cchNew++] = 'd';
1647 szNew[cchNew++] = 'o';
1648
1649 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
1650 but need to take into account that we might be at the end of the line before
1651 adding the space. */
1652 size_t offTodoAfter = offTodo + 4;
1653 if ( offTodoAfter < cchLine
1654 && pchLine[offTodoAfter] == ':')
1655 offTodoAfter++;
1656 if ( offTodoAfter < cchLine
1657 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
1658 offTodoAfter++;
1659 if (offTodoAfter < cchLine)
1660 szNew[cchNew++] = ' ';
1661
1662 /* Write it out. */
1663 ScmStreamWrite(pOut, pchLine, offCommentStart);
1664 ScmStreamWrite(pOut, szNew, cchNew);
1665 if (offTodoAfter < cchLine)
1666 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
1667 ScmStreamPutEol(pOut, enmEol);
1668
1669 /* Check whether we actually made any changes. */
1670 if ( cchNew != offTodoAfter - offCommentStart
1671 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
1672 cChanges++;
1673 continue;
1674 }
1675 }
1676
1677 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1678 if (RT_FAILURE(rc))
1679 return false;
1680 }
1681 if (cChanges > 0)
1682 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
1683 return cChanges != 0;
1684}
1685
1686
1687/**
1688 * Rewrite a C/C++ source or header file.
1689 *
1690 * @returns true if modifications were made, false if not.
1691 * @param pIn The input stream.
1692 * @param pOut The output stream.
1693 * @param pSettings The settings.
1694 *
1695 * @todo
1696 *
1697 * Ideas for C/C++:
1698 * - space after if, while, for, switch
1699 * - spaces in for (i=0;i<x;i++)
1700 * - complex conditional, bird style.
1701 * - remove unnecessary parentheses.
1702 * - sort defined RT_OS_*|| and RT_ARCH
1703 * - sizeof without parenthesis.
1704 * - defined without parenthesis.
1705 * - trailing spaces.
1706 * - parameter indentation.
1707 * - space after comma.
1708 * - while (x--); -> multi line + comment.
1709 * - else statement;
1710 * - space between function and left parenthesis.
1711 * - TODO, XXX, @todo cleanup.
1712 * - Space before/after '*'.
1713 * - ensure new line at end of file.
1714 * - Indentation of precompiler statements (#ifdef, #defines).
1715 * - space between functions.
1716 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
1717 */
1718bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1719{
1720
1721 RT_NOREF4(pState, pIn, pOut, pSettings);
1722 return false;
1723}
1724
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