summaryrefslogtreecommitdiffstats
path: root/src/bldprogs/scmrw.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/bldprogs/scmrw.cpp
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bldprogs/scmrw.cpp')
-rw-r--r--src/bldprogs/scmrw.cpp3229
1 files changed, 3229 insertions, 0 deletions
diff --git a/src/bldprogs/scmrw.cpp b/src/bldprogs/scmrw.cpp
new file mode 100644
index 00000000..aa76528f
--- /dev/null
+++ b/src/bldprogs/scmrw.cpp
@@ -0,0 +1,3229 @@
+/* $Id: scmrw.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** License types. */
+typedef enum SCMLICENSETYPE
+{
+ kScmLicenseType_Invalid = 0,
+ kScmLicenseType_OseGpl,
+ kScmLicenseType_OseDualGplCddl,
+ kScmLicenseType_OseCddl,
+ kScmLicenseType_VBoxLgpl,
+ kScmLicenseType_Mit,
+ kScmLicenseType_Confidential
+} SCMLICENSETYPE;
+
+/** A license. */
+typedef struct SCMLICENSETEXT
+{
+ /** The license type. */
+ SCMLICENSETYPE enmType;
+ /** The license option. */
+ SCMLICENSE enmOpt;
+ /** The license text. */
+ const char *psz;
+ /** The license text length. */
+ size_t cch;
+} SCMLICENSETEXT;
+/** Pointer to a license. */
+typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
+
+/**
+ * Copyright + license rewriter state.
+ */
+typedef struct SCMCOPYRIGHTINFO
+{
+ /** State. */
+ PSCMRWSTATE pState; /**< input */
+ /** The comment style (neede for C/C++). */
+ SCMCOMMENTSTYLE enmCommentStyle; /**< input */
+
+ /** Number of comments we've parsed. */
+ uint32_t cComments;
+
+ /** Copy of the contributed-by line if present. */
+ char *pszContributedBy;
+
+ /** @name Common info
+ * @{ */
+ uint32_t iLineComment;
+ uint32_t cLinesComment; /**< This excludes any external license lines. */
+ /** @} */
+
+ /** @name Copyright info
+ * @{ */
+ uint32_t iLineCopyright;
+ uint32_t uFirstYear;
+ uint32_t uLastYear;
+ bool fWellFormedCopyright;
+ bool fUpToDateCopyright;
+ /** @} */
+
+ /** @name License info
+ * @{ */
+ bool fOpenSource; /**< input */
+ PCSCMLICENSETEXT pExpectedLicense; /**< input */
+ PCSCMLICENSETEXT paLicenses; /**< input */
+ SCMLICENSE enmLicenceOpt; /**< input */
+ uint32_t iLineLicense;
+ uint32_t cLinesLicense;
+ PCSCMLICENSETEXT pCurrentLicense;
+ bool fIsCorrectLicense;
+ bool fWellFormedLicense;
+ bool fExternalLicense;
+ /** @} */
+
+ /** @name LGPL licence notice and disclaimer info
+ * @{ */
+ /** Wheter to check for LGPL license notices and disclaimers. */
+ bool fCheckforLgpl;
+ /** The approximate line we found the (first) LGPL licence notice on. */
+ uint32_t iLineLgplNotice;
+ /** The line number after the LGPL notice comment. */
+ uint32_t iLineAfterLgplComment;
+ /** The LGPL disclaimer line. */
+ uint32_t iLineLgplDisclaimer;
+ /** @} */
+
+} SCMCOPYRIGHTINFO;
+typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** --license-ose-gpl */
+static const char g_szVBoxOseGpl[] =
+ "This file is part of VirtualBox Open Source Edition (OSE), as\n"
+ "available from http://www.virtualbox.org. This file is free software;\n"
+ "you can redistribute it and/or modify it under the terms of the GNU\n"
+ "General Public License (GPL) as published by the Free Software\n"
+ "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
+ "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
+ "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
+
+/** --license-ose-dual */
+static const char g_szVBoxOseDualGplCddl[] =
+ "This file is part of VirtualBox Open Source Edition (OSE), as\n"
+ "available from http://www.virtualbox.org. This file is free software;\n"
+ "you can redistribute it and/or modify it under the terms of the GNU\n"
+ "General Public License (GPL) as published by the Free Software\n"
+ "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
+ "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
+ "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
+ "\n"
+ "The contents of this file may alternatively be used under the terms\n"
+ "of the Common Development and Distribution License Version 1.0\n"
+ "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
+ "VirtualBox OSE distribution, in which case the provisions of the\n"
+ "CDDL are applicable instead of those of the GPL.\n"
+ "\n"
+ "You may elect to license modified versions of this file under the\n"
+ "terms and conditions of either the GPL or the CDDL or both.\n";
+
+/** --license-ose-cddl */
+static const char g_szVBoxOseCddl[] =
+ "This file is part of VirtualBox Open Source Edition (OSE), as\n"
+ "available from http://www.virtualbox.org. This file is free software;\n"
+ "you can redistribute it and/or modify it under the terms of the Common\n"
+ "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
+ "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
+ "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
+ "WITHOUT ANY WARRANTY of any kind.\n";
+
+/** --license-lgpl */
+static const char g_szVBoxLgpl[] =
+ "This file is part of a free software library; you can redistribute\n"
+ "it and/or modify it under the terms of the GNU Lesser General\n"
+ "Public License version 2.1 as published by the Free Software\n"
+ "Foundation and shipped in the \"COPYING\" file with this library.\n"
+ "The library is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY of any kind.\n"
+ "\n"
+ "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
+ "any license choice other than GPL or LGPL is available it will\n"
+ "apply instead, Oracle elects to use only the Lesser General Public\n"
+ "License version 2.1 (LGPLv2) at this time for any software where\n"
+ "a choice of LGPL license versions is made available with the\n"
+ "language indicating that LGPLv2 or any later version may be used,\n"
+ "or where a choice of which version of the LGPL is applied is\n"
+ "otherwise unspecified.\n";
+
+/** --license-mit
+ * @note This isn't detectable as VirtualBox or Oracle specific.
+ */
+static const char g_szMit[] =
+ "Permission is hereby granted, free of charge, to any person\n"
+ "obtaining a copy of this software and associated documentation\n"
+ "files (the \"Software\"), to deal in the Software without\n"
+ "restriction, including without limitation the rights to use,\n"
+ "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
+ "copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following\n"
+ "conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be\n"
+ "included in all copies or substantial portions of the Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
+ "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
+ "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
+ "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
+ "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
+ "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
+ "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
+ "OTHER DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-mit, alternative wording \#1.
+ * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
+ * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
+ * couple of lines shorter. */
+static const char g_szMitAlt1[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be included in\n"
+ "all copies or substantial portions of the Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
+ "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
+ "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
+ "OTHER DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-mit, alternative wording \#2.
+ * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
+ * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
+ * Its layout is wider, so it is a couple of lines shorter. */
+static const char g_szMitAlt2[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be included in\n"
+ "all copies or substantial portions of the Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
+ "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
+ "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
+ "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-mit, alternative wording \#3.
+ * @note This differes from g_szMitAlt2 in that the second and third sections
+ * have been switch. */
+static const char g_szMitAlt3[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
+ "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
+ "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
+ "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be included in\n"
+ "all copies or substantial portions of the Software.\n";
+
+/** --license-(based-on)mit, alternative wording \#4.
+ * @note This differs from g_szMitAlt2 in injecting "(including the next
+ * paragraph)". */
+static const char g_szMitAlt4[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice (including the next\n"
+ "paragraph) shall be included in all copies or substantial portions of the\n"
+ "Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
+ "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
+ "DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-(based-on)mit, alternative wording \#5.
+ * @note This differs from g_szMitAlt3 in using "sub license" instead of
+ * "sublicense" and adding an illogical "(including the next
+ * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
+static const char g_szMitAlt5[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the\n"
+ "\"Software\"), to deal in the Software without restriction, including\n"
+ "without limitation the rights to use, copy, modify, merge, publish,\n"
+ "distribute, sub license, and/or sell copies of the Software, and to\n"
+ "permit persons to whom the Software is furnished to do so, subject to\n"
+ "the following conditions:\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
+ "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
+ "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
+ "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
+ "\n"
+ "The above copyright notice and this permission notice (including the\n"
+ "next paragraph) shall be included in all copies or substantial portions\n"
+ "of the Software.\n";
+
+/** Oracle confidential. */
+static const char g_szOracleConfidential[] =
+ "Oracle Corporation confidential\n"
+ "All rights reserved\n";
+
+/** Licenses to detect when --license-mit isn't used. */
+static const SCMLICENSETEXT g_aLicenses[] =
+{
+ { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
+ { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
+ { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
+ { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
+ { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
+ { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
+};
+
+/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
+static const SCMLICENSETEXT g_aLicensesWithMit[] =
+{
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
+ { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
+ { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
+ { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
+ { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
+ { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
+};
+
+/** Copyright holder. */
+static const char g_szCopyrightHolder[] = "Oracle Corporation";
+
+/** LGPL disclaimer. */
+static const char g_szLgplDisclaimer[] =
+ "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
+ "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
+ "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
+ "a choice of LGPL license versions is made available with the language indicating\n"
+ "that LGPLv2 or any later version may be used, or where a choice of which version\n"
+ "of the LGPL is applied is otherwise unspecified.\n";
+
+/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentStart[] =
+{
+ { RT_STR_TUPLE("<invalid> ") },
+ { RT_STR_TUPLE("/*") },
+ { RT_STR_TUPLE("#") },
+ { RT_STR_TUPLE("\"\"\"") },
+ { RT_STR_TUPLE(";") },
+ { RT_STR_TUPLE("REM") },
+ { RT_STR_TUPLE("rem") },
+ { RT_STR_TUPLE("Rem") },
+ { RT_STR_TUPLE("--") },
+ { RT_STR_TUPLE("'") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
+{
+ { RT_STR_TUPLE("<invalid> ") },
+ { RT_STR_TUPLE(" * ") },
+ { RT_STR_TUPLE("# ") },
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE("; ") },
+ { RT_STR_TUPLE("REM ") },
+ { RT_STR_TUPLE("rem ") },
+ { RT_STR_TUPLE("Rem ") },
+ { RT_STR_TUPLE("-- ") },
+ { RT_STR_TUPLE("' ") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
+{
+ { RT_STR_TUPLE("<invalid>") },
+ { RT_STR_TUPLE(" *") },
+ { RT_STR_TUPLE("#") },
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE(";") },
+ { RT_STR_TUPLE("REM") },
+ { RT_STR_TUPLE("rem") },
+ { RT_STR_TUPLE("Rem") },
+ { RT_STR_TUPLE("--") },
+ { RT_STR_TUPLE("'") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
+{
+ { RT_STR_TUPLE("<invalid> ") },
+ { RT_STR_TUPLE(" */") },
+ { RT_STR_TUPLE("#") },
+ { RT_STR_TUPLE("\"\"\"") },
+ { RT_STR_TUPLE(";") },
+ { RT_STR_TUPLE("REM") },
+ { RT_STR_TUPLE("rem") },
+ { RT_STR_TUPLE("Rem") },
+ { RT_STR_TUPLE("--") },
+ { RT_STR_TUPLE("'") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+
+/**
+ * Figures out the predominant casing of the "REM" keyword in a batch file.
+ *
+ * @returns Predominant comment style.
+ * @param pIn The file to scan. Will be rewound.
+ */
+static SCMCOMMENTSTYLE determinBatchFileCommentStyle(PSCMSTREAM pIn)
+{
+ /*
+ * Figure out whether it's using upper or lower case REM comments before
+ * doing the work.
+ */
+ uint32_t cUpper = 0;
+ uint32_t cLower = 0;
+ uint32_t cCamel = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ while ( cchLine > 2
+ && RT_C_IS_SPACE(*pchLine))
+ {
+ pchLine++;
+ cchLine--;
+ }
+ if ( ( cchLine > 3
+ && RT_C_IS_SPACE(pchLine[2]))
+ || cchLine == 3)
+ {
+ if ( pchLine[0] == 'R'
+ && pchLine[1] == 'E'
+ && pchLine[2] == 'M')
+ cUpper++;
+ else if ( pchLine[0] == 'r'
+ && pchLine[1] == 'e'
+ && pchLine[2] == 'm')
+ cLower++;
+ else if ( pchLine[0] == 'R'
+ && pchLine[1] == 'e'
+ && pchLine[2] == 'm')
+ cCamel++;
+ }
+ }
+
+ ScmStreamRewindForReading(pIn);
+
+ if (cLower >= cUpper && cLower >= cCamel)
+ return kScmCommentStyle_Rem_Lower;
+ if (cCamel >= cLower && cCamel >= cUpper)
+ return kScmCommentStyle_Rem_Camel;
+ return kScmCommentStyle_Rem_Upper;
+}
+
+
+/**
+ * Worker for isBlankLine.
+ *
+ * @returns true if blank, false if not.
+ * @param pchLine Pointer to the start of the line.
+ * @param cchLine The (encoded) length of the line, excluding EOL char.
+ */
+static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
+{
+ /*
+ * From the end, more likely to hit a non-blank char there.
+ */
+ while (cchLine-- > 0)
+ if (!RT_C_IS_BLANK(pchLine[cchLine]))
+ return false;
+ return true;
+}
+
+/**
+ * Helper for checking whether a line is blank.
+ *
+ * @returns true if blank, false if not.
+ * @param pchLine Pointer to the start of the line.
+ * @param cchLine The (encoded) length of the line, excluding EOL char.
+ */
+DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
+{
+ if (cchLine == 0)
+ return true;
+ /*
+ * We're more likely to fine a non-space char at the end of the line than
+ * at the start, due to source code indentation.
+ */
+ if (pchLine[cchLine - 1])
+ return false;
+
+ /*
+ * Don't bother inlining loop code.
+ */
+ return isBlankLineSlow(pchLine, cchLine);
+}
+
+
+/**
+ * Checks if there are @a cch blanks at @a pch.
+ *
+ * @returns true if span of @a cch blanks, false if not.
+ * @param pch The start of the span to check.
+ * @param cch The length of the span.
+ */
+DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch)
+{
+ while (cch-- > 0)
+ {
+ char const ch = *pch++;
+ if (!RT_C_IS_BLANK(ch))
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Strip trailing blanks (space & tab).
+ *
+ * @returns True if modified, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fStripTrailingBlanks)
+ return false;
+
+ bool fModified = false;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ int rc;
+ if ( cchLine == 0
+ || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ else
+ {
+ cchLine--;
+ while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
+ cchLine--;
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ fModified = true;
+ }
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
+ return fModified;
+}
+
+/**
+ * Expand tabs.
+ *
+ * @returns True if modified, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fConvertTabs)
+ return false;
+
+ size_t const cchTab = pSettings->cchTab;
+ bool fModified = false;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ int rc;
+ const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
+ if (!pchTab)
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ else
+ {
+ size_t offTab = 0;
+ const char *pchChunk = pchLine;
+ for (;;)
+ {
+ size_t cchChunk = pchTab - pchChunk;
+ offTab += cchChunk;
+ ScmStreamWrite(pOut, pchChunk, cchChunk);
+
+ size_t cchToTab = cchTab - offTab % cchTab;
+ ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
+ offTab += cchToTab;
+
+ pchChunk = pchTab + 1;
+ size_t cchLeft = cchLine - (pchChunk - pchLine);
+ pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
+ if (!pchTab)
+ {
+ rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
+ break;
+ }
+ }
+
+ fModified = true;
+ }
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Expanded tabs\n");
+ return fModified;
+}
+
+/**
+ * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ * @param enmDesiredEol The desired end of line indicator type.
+ * @param pszDesiredSvnEol The desired svn:eol-style.
+ */
+static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
+ SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
+{
+ if (!pSettings->fConvertEol)
+ return false;
+
+ bool fModified = false;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ if ( enmEol != enmDesiredEol
+ && enmEol != SCMEOL_NONE)
+ {
+ fModified = true;
+ enmEol = enmDesiredEol;
+ }
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Converted EOL markers\n");
+
+ /* Check svn:eol-style if appropriate */
+ if ( pSettings->fSetSvnEol
+ && ScmSvnIsInWorkingCopy(pState))
+ {
+ char *pszEol;
+ int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
+ if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
+ || rc == VERR_NOT_FOUND)
+ {
+ if (rc == VERR_NOT_FOUND)
+ ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
+ else
+ ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
+ int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
+ if (RT_FAILURE(rc2))
+ ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
+ }
+ if (RT_SUCCESS(rc))
+ RTStrFree(pszEol);
+ }
+
+ /** @todo also check the subversion svn:eol-style state! */
+ return fModified;
+}
+
+/**
+ * Force native end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
+#else
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
+#endif
+}
+
+/**
+ * Force the stream to use LF as the end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
+}
+
+/**
+ * Force the stream to use CRLF as the end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
+}
+
+/**
+ * Strip trailing blank lines and/or make sure there is exactly one blank line
+ * at the end of the file.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @remarks ASSUMES trailing white space has been removed already.
+ */
+bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if ( !pSettings->fStripTrailingLines
+ && !pSettings->fForceTrailingLine
+ && !pSettings->fForceFinalEol)
+ return false;
+
+ size_t const cLines = ScmStreamCountLines(pIn);
+
+ /* Empty files remains empty. */
+ if (cLines <= 1)
+ return false;
+
+ /* Figure out if we need to adjust the number of lines or not. */
+ size_t cLinesNew = cLines;
+
+ if ( pSettings->fStripTrailingLines
+ && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
+ {
+ while ( cLinesNew > 1
+ && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
+ cLinesNew--;
+ }
+
+ if ( pSettings->fForceTrailingLine
+ && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
+ cLinesNew++;
+
+ bool fFixMissingEol = pSettings->fForceFinalEol
+ && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
+
+ if ( !fFixMissingEol
+ && cLines == cLinesNew)
+ return false;
+
+ /* Copy the number of lines we've arrived at. */
+ ScmStreamRewindForReading(pIn);
+
+ size_t cCopied = RT_MIN(cLinesNew, cLines);
+ ScmStreamCopyLines(pOut, pIn, cCopied);
+
+ if (cCopied != cLinesNew)
+ {
+ while (cCopied++ < cLinesNew)
+ ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
+ }
+ /* Fix missing EOL if required. */
+ else if (fFixMissingEol)
+ {
+ if (ScmStreamGetEol(pIn) == SCMEOL_LF)
+ ScmStreamWrite(pOut, "\n", 1);
+ else
+ ScmStreamWrite(pOut, "\r\n", 2);
+ }
+
+ ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
+ return true;
+}
+
+/**
+ * Make sure there is no svn:executable property on the current file.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing svn:executable\n");
+ rc = ScmSvnDelProperty(pState, "svn:executable");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ return false;
+}
+
+/**
+ * Make sure there is no svn:keywords property on the current file.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing svn:keywords\n");
+ rc = ScmSvnDelProperty(pState, "svn:keywords");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ return false;
+}
+
+/**
+ * Make sure there is no svn:eol-style property on the current file.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing svn:eol-style\n");
+ rc = ScmSvnDelProperty(pState, "svn:eol-style");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ return false;
+}
+
+/**
+ * Makes sure the svn properties are appropriate for a binary.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ /* remove svn:eol-style and svn:keywords */
+ static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
+ {
+ char *pszValue;
+ int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
+ RTStrFree(pszValue);
+ rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
+ }
+ else if (rc != VERR_NOT_FOUND)
+ ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
+ }
+
+ /* Make sure there is a svn:mime-type set. */
+ int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
+ if (rc == VERR_NOT_FOUND)
+ {
+ ScmVerbose(pState, 2, " * settings svn:mime-type\n");
+ rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ else if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
+
+ return false;
+}
+
+/**
+ * Make sure the Id and Revision keywords are expanded.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnKeywords
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ char *pszKeywords;
+ int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
+ if ( RT_SUCCESS(rc)
+ && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
+ || !strstr(pszKeywords, "Revision")) )
+ {
+ if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
+ rc = RTStrAAppend(&pszKeywords, " Id Revision");
+ else if (!strstr(pszKeywords, "Id"))
+ rc = RTStrAAppend(&pszKeywords, " Id");
+ else
+ rc = RTStrAAppend(&pszKeywords, " Revision");
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
+ rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ else
+ ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
+ RTStrFree(pszKeywords);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
+ rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ else if (RT_SUCCESS(rc))
+ RTStrFree(pszKeywords);
+
+ return false;
+}
+
+/**
+ * Checks the svn:sync-process value and that parent is exported too.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( pSettings->fSkipSvnSyncProcess
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ char *pszSyncProcess;
+ int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
+ if (RT_SUCCESS(rc))
+ {
+ if (strcmp(pszSyncProcess, "export") == 0)
+ {
+ char *pszParentSyncProcess;
+ rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
+ if (RT_SUCCESS(rc))
+ {
+ if (strcmp(pszSyncProcess, "export") != 0)
+ ScmError(pState, VERR_INVALID_STATE,
+ "svn:sync-process=export, but parent directory differs: %s\n"
+ "WARNING! Make sure to unexport everything inside the directory first!\n"
+ " Then you may export the directory and stuff inside it if you want.\n"
+ " (Just exporting the directory will not make anything inside it externally visible.)\n"
+ , pszParentSyncProcess);
+ RTStrFree(pszParentSyncProcess);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ ScmError(pState, VERR_NOT_FOUND,
+ "svn:sync-process=export, but parent directory is not exported!\n"
+ "WARNING! Make sure to unexport everything inside the directory first!\n"
+ " Then you may export the directory and stuff inside it if you want.\n"
+ " (Just exporting the directory will not make anything inside it externally visible.)\n");
+ else
+ ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
+ }
+ else if (strcmp(pszSyncProcess, "ignore") != 0)
+ ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
+ RTStrFree(pszSyncProcess);
+ }
+ else if (rc != VERR_NOT_FOUND)
+ ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
+
+ return false;
+}
+
+/**
+ * Compares two strings word-by-word, ignoring spaces, punctuation and case.
+ *
+ * Assumes ASCII strings.
+ *
+ * @returns true if they match, false if not.
+ * @param psz1 The first string. This is typically the known one.
+ * @param psz2 The second string. This is typically the unknown one,
+ * which is why we return a next pointer for this one.
+ * @param ppsz2Next Where to return the next part of the 2nd string. If
+ * this is NULL, the whole string must match.
+ */
+static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
+{
+ for (;;)
+ {
+ /* Try compare raw strings first. */
+ char ch1 = *psz1;
+ char ch2 = *psz2;
+ if ( ch1 == ch2
+ || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
+ {
+ if (ch1)
+ {
+ psz1++;
+ psz2++;
+ }
+ else
+ {
+ if (ppsz2Next)
+ *ppsz2Next = psz2;
+ return true;
+ }
+ }
+ else
+ {
+ /* Try skip spaces an punctuation. */
+ while ( RT_C_IS_SPACE(ch1)
+ || RT_C_IS_PUNCT(ch1))
+ ch1 = *++psz1;
+
+ if (ch1 == '\0' && ppsz2Next)
+ {
+ *ppsz2Next = psz2;
+ return true;
+ }
+
+ while ( RT_C_IS_SPACE(ch2)
+ || RT_C_IS_PUNCT(ch2))
+ ch2 = *++psz2;
+
+ if ( ch1 != ch2
+ && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
+ {
+ if (ppsz2Next)
+ *ppsz2Next = psz2;
+ return false;
+ }
+ }
+ }
+}
+
+/**
+ * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
+ * and case.
+ *
+ * @returns true if found, false if not.
+ * @param pszText The haystack to search in.
+ * @param cchText The length @a pszText.
+ * @param pszFragment The needle to search for.
+ * @param ppszStart Where to return the address in @a pszText where
+ * the fragment was found. Optional.
+ * @param ppszNext Where to return the pointer to the first char in
+ * @a pszText after the fragment. Optional.
+ *
+ * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
+ * This character must not be space or punctuation.
+ */
+static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
+ const char **ppszStart, const char **ppszNext)
+{
+ Assert(!((unsigned)*pszFragment & 0x80));
+ Assert(pszText[cchText] == '\0');
+ Assert(!RT_C_IS_BLANK(*pszFragment));
+ Assert(!RT_C_IS_PUNCT(*pszFragment));
+
+ char chLower = RT_C_TO_LOWER(*pszFragment);
+ char chUpper = RT_C_TO_UPPER(*pszFragment);
+ for (;;)
+ {
+ const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
+ const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
+ if (!pszHit && !pszHit2)
+ {
+ if (ppszStart)
+ *ppszStart = NULL;
+ if (ppszNext)
+ *ppszNext = NULL;
+ return false;
+ }
+
+ if ( pszHit == NULL
+ || ( pszHit2 != NULL
+ && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
+ pszHit = pszHit2;
+
+ const char *pszNext;
+ if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
+ {
+ if (ppszStart)
+ *ppszStart = pszHit;
+ if (ppszNext)
+ *ppszNext = pszNext;
+ return true;
+ }
+
+ cchText -= pszHit - pszText + 1;
+ pszText = pszHit + 1;
+ }
+}
+
+
+/**
+ * Counts the number of lines in the given substring.
+ *
+ * @returns The number of lines.
+ * @param psz The start of the substring.
+ * @param cch The length of the substring.
+ */
+static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
+{
+ uint32_t cLines = 0;
+ for (;;)
+ {
+ const char *pszEol = (const char *)memchr(psz, '\n', cch);
+ if (pszEol)
+ cLines++;
+ else
+ return cLines + (*psz != '\0');
+ cch -= pszEol + 1 - psz;
+ if (!cch)
+ return cLines;
+ psz = pszEol + 1;
+ }
+}
+
+
+/**
+ * Comment parser callback for locating copyright and license.
+ */
+static DECLCALLBACK(int)
+rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
+{
+ PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
+ Assert(strlen(pszBody) == cchBody);
+ //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
+ ScmVerbose(pState->pState, 5,
+ "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
+ pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
+ pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
+
+ pState->cComments++;
+
+ uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
+
+ /*
+ * Look for a 'contributed by' or 'includes contributions from' line, these
+ * comes first when present.
+ */
+ const char *pchContributedBy = NULL;
+ size_t cchContributedBy = 0;
+ size_t cBlankLinesAfterContributedBy = 0;
+ if ( pState->pszContributedBy == NULL
+ && ( pState->iLineCopyright == UINT32_MAX
+ || pState->iLineLicense == UINT32_MAX)
+ && ( ( cchBody > sizeof("Contributed by")
+ && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
+ || ( cchBody > sizeof("Includes contributions from")
+ && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
+ {
+ const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
+ while (pszNextLine && pszNextLine[1] != '\n')
+ pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
+ if (pszNextLine)
+ {
+ pchContributedBy = pszBody;
+ cchContributedBy = pszNextLine - pszBody;
+
+ /* Skip the copyright line and any blank lines following it. */
+ cchBody -= cchContributedBy + 1;
+ pszBody = pszNextLine + 1;
+ iLine += 1;
+ while (*pszBody == '\n')
+ {
+ pszBody++;
+ cchBody--;
+ iLine++;
+ cBlankLinesAfterContributedBy++;
+ }
+ }
+ }
+
+ /*
+ * Look for the copyright line.
+ */
+ bool fFoundCopyright = false;
+ uint32_t cBlankLinesAfterCopyright = 0;
+ if ( pState->iLineCopyright == UINT32_MAX
+ && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
+ && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
+ {
+ const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
+
+ /* Oracle copyright? */
+ const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
+ while (RT_C_IS_SPACE(pszEnd[-1]))
+ pszEnd--;
+ if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
+ && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
+ && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
+ {
+ /* Parse out the year(s). */
+ const char *psz = pszBody + sizeof("copyright");
+ while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
+ psz++;
+ if (RT_C_IS_DIGIT(*psz))
+ {
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
+ if ( RT_SUCCESS(rc)
+ && rc != VWRN_NUMBER_TOO_BIG
+ && rc != VWRN_NEGATIVE_UNSIGNED)
+ {
+ if ( pState->uFirstYear < 1975
+ || pState->uFirstYear > 3000)
+ {
+ ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
+ pState->uFirstYear, pszEnd - pszBody, pszBody);
+ pState->uFirstYear = UINT32_MAX;
+ }
+
+ while (RT_C_IS_SPACE(*pszNext))
+ pszNext++;
+ if (*pszNext == '-')
+ {
+ do
+ pszNext++;
+ while (RT_C_IS_SPACE(*pszNext));
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
+ if ( RT_SUCCESS(rc)
+ && rc != VWRN_NUMBER_TOO_BIG
+ && rc != VWRN_NEGATIVE_UNSIGNED)
+ {
+ if ( pState->uLastYear < 1975
+ || pState->uLastYear > 3000)
+ {
+ ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
+ pState->uLastYear, pszEnd - pszBody, pszBody);
+ pState->uLastYear = UINT32_MAX;
+ }
+ else if (pState->uFirstYear > pState->uLastYear)
+ {
+ RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
+ uint32_t iTmp = pState->uLastYear;
+ pState->uLastYear = pState->uFirstYear;
+ pState->uFirstYear = iTmp;
+ }
+ }
+ else
+ {
+ pState->uLastYear = UINT32_MAX;
+ ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
+ "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
+ }
+ }
+ else if (*pszNext != g_szCopyrightHolder[0])
+ ScmError(pState->pState, VERR_PARSE_ERROR,
+ "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
+ else
+ pState->uLastYear = pState->uFirstYear;
+ }
+ else
+ {
+ pState->uFirstYear = UINT32_MAX;
+ ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
+ "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
+ }
+ }
+
+ /* The copyright comment must come before the license. */
+ if (pState->iLineLicense != UINT32_MAX)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
+ iLine, pState->iLineLicense);
+
+ /* In C/C++ code, this must be a multiline comment. While in python it
+ must be a */
+ if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
+ else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
+
+ /* The copyright must be followed by the license. */
+ if (!pszNextLine)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
+
+ /* Quit if we've flagged a failure. */
+ if (RT_FAILURE(pState->pState->rc))
+ return VERR_CALLBACK_RETURN;
+
+ /* Check if it's well formed and up to date. */
+ char szWellFormed[256];
+ size_t cchWellFormed;
+ if (pState->uFirstYear == pState->uLastYear)
+ cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
+ pState->uFirstYear, g_szCopyrightHolder);
+ else
+ cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
+ pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
+ pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
+ pState->iLineCopyright = iLine;
+ pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
+ && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
+ if (!pState->fWellFormedCopyright)
+ ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
+
+ /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
+ if (pInfo->cBlankLinesBefore != 1)
+ {
+ ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
+ pInfo->cBlankLinesBefore);
+ pState->fWellFormedCopyright = false;
+ }
+
+ /* If the comment doesn't start in column 1, trigger rewrite. */
+ if (pInfo->offStart != 0)
+ {
+ ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
+ pState->fWellFormedCopyright = false;
+ /** @todo check that there isn't any code preceeding the comment. */
+ }
+
+ if (pchContributedBy)
+ {
+ pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
+ if (cBlankLinesAfterContributedBy != 1)
+ {
+ ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
+ cBlankLinesAfterContributedBy);
+ pState->fWellFormedCopyright = false;
+ }
+ }
+
+ fFoundCopyright = true;
+ ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
+ pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
+ }
+ else
+ ScmVerbose(pState->pState, 3, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
+
+ if (!pszNextLine)
+ return VINF_SUCCESS;
+
+ /* Skip the copyright line and any blank lines following it. */
+ cchBody -= pszNextLine - pszBody + 1;
+ pszBody = pszNextLine + 1;
+ iLine += 1;
+ while (*pszBody == '\n')
+ {
+ pszBody++;
+ cchBody--;
+ iLine++;
+ cBlankLinesAfterCopyright++;
+ }
+
+ /*
+ * If we have a based-on-mit scenario, check for the lead in now and
+ * complain if not found.
+ */
+ if ( fFoundCopyright
+ && pState->enmLicenceOpt == kScmLicense_BasedOnMit
+ && pState->iLineLicense == UINT32_MAX)
+ {
+ if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
+ {
+ /* Take down a comment area which goes up to 'this file is based on'.
+ The license line and length isn't used but gets set to cover the current line. */
+ pState->iLineComment = pInfo->iLineStart;
+ pState->cLinesComment = iLine - pInfo->iLineStart;
+ pState->iLineLicense = iLine;
+ pState->cLinesLicense = 1;
+ pState->fExternalLicense = true;
+ pState->fIsCorrectLicense = true;
+ pState->fWellFormedLicense = true;
+
+ /* Check if we've got a MIT a license here or not. */
+ pState->pCurrentLicense = NULL;
+ do
+ {
+ const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
+ if (!pszEol || pszEol[1] == '\0')
+ {
+ pszBody += cchBody;
+ cchBody = 0;
+ break;
+ }
+ cchBody -= pszEol - pszBody + 1;
+ pszBody = pszEol + 1;
+ iLine++;
+
+ for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
+ {
+ const char *pszNext;
+ if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
+ && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
+ {
+ pState->pCurrentLicense = pCur;
+ break;
+ }
+ }
+ } while (!pState->pCurrentLicense);
+ if (!pState->pCurrentLicense)
+ ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
+ else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
+ ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
+ pState->pCurrentLicense->psz);
+ }
+ else
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Look for LGPL like text in the comment.
+ */
+ if (pState->fCheckforLgpl && cchBody > 128)
+ {
+ /* We look for typical LGPL notices. */
+ if (pState->iLineLgplNotice == UINT32_MAX)
+ {
+ static const char * const s_apszFragments[] =
+ {
+ "under the terms of the GNU Lesser General Public License",
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
+ if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
+ {
+ pState->iLineLgplNotice = iLine;
+ pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
+ ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
+ break;
+ }
+ }
+
+ if ( pState->iLineLgplDisclaimer == UINT32_MAX
+ && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
+ {
+ pState->iLineLgplDisclaimer = iLine;
+ ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
+ }
+ }
+
+ /*
+ * Look for the license text.
+ */
+ if (pState->iLineLicense == UINT32_MAX)
+ {
+ for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
+ {
+ const char *pszNext;
+ if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
+ && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
+ {
+ while ( RT_C_IS_SPACE(*pszNext)
+ || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
+ pszNext++;
+
+ uint32_t cDashes = 0;
+ while (*pszNext == '-')
+ cDashes++, pszNext++;
+ bool fExternal = cDashes > 10;
+
+ if ( *pszNext == '\0'
+ || fExternal)
+ {
+ /* In C/C++ code, this must be a multiline comment. While in python it
+ must be a */
+ if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
+ else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
+
+ /* Quit if we've flagged a failure. */
+ if (RT_FAILURE(pState->pState->rc))
+ return VERR_CALLBACK_RETURN;
+
+ /* Record it. */
+ pState->iLineLicense = iLine;
+ pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
+ pState->pCurrentLicense = pCur;
+ pState->fExternalLicense = fExternal;
+ pState->fIsCorrectLicense = pState->fOpenSource
+ ? pCur == pState->pExpectedLicense
+ : pCur->enmType == kScmLicenseType_Confidential;
+ pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
+ if (!pState->fWellFormedLicense)
+ ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
+
+ /* If there was more than one blank line between the copyright and the
+ license text, extend the license text area and force a rewrite of it. */
+ if (cBlankLinesAfterCopyright > 1)
+ {
+ ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
+ cBlankLinesAfterCopyright);
+ pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
+ pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
+ pState->fWellFormedLicense = false;
+ }
+
+ /* If there was more than one blank line after the license, trigger a rewrite. */
+ if (!fExternal && pInfo->cBlankLinesAfter != 1)
+ {
+ ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
+ pInfo->cBlankLinesAfter);
+ pState->fWellFormedLicense = false;
+ }
+
+ /** @todo Check that the last comment line doesn't have any code on it. */
+ /** @todo Check that column 2 contains '*' for C/C++ files. */
+
+ ScmVerbose(pState->pState, 3,
+ "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
+ pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
+ pState->fIsCorrectLicense, pState->fWellFormedLicense,
+ pState->fExternalLicense, pState->fOpenSource);
+
+ if (fFoundCopyright)
+ {
+ pState->iLineComment = pInfo->iLineStart;
+ pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
+ - pInfo->iLineStart;
+ }
+ else
+ ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
+ break;
+ }
+ }
+ }
+ }
+
+ if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
+
+ /*
+ * Stop looking for stuff after 100 comments.
+ */
+ if (pState->cComments > 100)
+ return VERR_CALLBACK_RETURN;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes comment body text.
+ *
+ * @returns Stream status.
+ * @param pOut The output stream.
+ * @param pszText The text to write.
+ * @param cchText The length of the text.
+ * @param enmCommentStyle The comment style.
+ * @param enmEol The EOL style.
+ */
+static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
+ SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
+{
+ Assert(pszText[cchText - 1] == '\n');
+ Assert(pszText[cchText - 2] != '\n');
+ NOREF(cchText);
+ do
+ {
+ const char *pszEol = strchr(pszText, '\n');
+ if (pszEol != pszText)
+ {
+ ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
+ g_aCopyrightCommentPrefix[enmCommentStyle].cch);
+ ScmStreamWrite(pOut, pszText, pszEol - pszText);
+ ScmStreamPutEol(pOut, enmEol);
+ }
+ else
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
+ g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
+ pszText = pszEol + 1;
+ } while (*pszText != '\0');
+ return ScmStreamGetStatus(pOut);
+}
+
+
+/**
+ * Updates the copyright year and/or license text.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ * @param enmCommentStyle The comment style used by the file.
+ */
+static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
+ SCMCOMMENTSTYLE enmCommentStyle)
+{
+ if ( !pSettings->fUpdateCopyrightYear
+ && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
+ return false;
+
+ /*
+ * Try locate the relevant comments.
+ */
+ SCMCOPYRIGHTINFO Info =
+ {
+ /*.pState = */ pState,
+ /*.enmCommentStyle = */ enmCommentStyle,
+
+ /*.cComments = */ 0,
+
+ /*.pszContributedBy = */ NULL,
+
+ /*.iLineComment = */ UINT32_MAX,
+ /*.cLinesComment = */ 0,
+
+ /*.iLineCopyright = */ UINT32_MAX,
+ /*.uFirstYear = */ UINT32_MAX,
+ /*.uLastYear = */ UINT32_MAX,
+ /*.fWellFormedCopyright = */ false,
+ /*.fUpToDateCopyright = */ false,
+
+ /*.fOpenSource = */ true,
+ /*.pExpectedLicense = */ NULL,
+ /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
+ && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
+ ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
+ /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
+ /*.iLineLicense = */ UINT32_MAX,
+ /*.cLinesLicense = */ 0,
+ /*.pCurrentLicense = */ NULL,
+ /*.fIsCorrectLicense = */ false,
+ /*.fWellFormedLicense = */ false,
+ /*.fExternalLicense = */ false,
+
+ /*.fCheckForLgpl = */ true,
+ /*.iLineLgplNotice = */ UINT32_MAX,
+ /*.iLineAfterLgplComment = */ UINT32_MAX,
+ /*.iLineLgplDisclaimer = */ UINT32_MAX,
+ };
+
+ /* Figure Info.fOpenSource and the desired license: */
+ char *pszSyncProcess;
+ int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
+ if (RT_SUCCESS(rc))
+ {
+ Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
+ RTStrFree(pszSyncProcess);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ Info.fOpenSource = false;
+ else
+ return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
+
+ Info.pExpectedLicense = Info.paLicenses;
+ if (Info.fOpenSource)
+ {
+ if ( pSettings->enmUpdateLicense != kScmLicense_Mit
+ && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
+ while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
+ Info.pExpectedLicense++;
+ else
+ Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
+ }
+ else
+ while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
+ Info.pExpectedLicense++;
+
+ /* Scan the comments. */
+ rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
+ if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
+ && RT_SUCCESS(pState->rc))
+ {
+ /*
+ * Do conformity checks.
+ */
+ bool fAddLgplDisclaimer = false;
+ if (Info.fCheckforLgpl)
+ {
+ if ( Info.iLineLgplNotice != UINT32_MAX
+ && Info.iLineLgplDisclaimer == UINT32_MAX)
+ {
+ if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
+ ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
+ Info.iLineLgplNotice + 1);
+ else
+ {
+ ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
+ fAddLgplDisclaimer = true;
+ }
+ }
+ else if ( Info.iLineLgplNotice == UINT32_MAX
+ && Info.iLineLgplDisclaimer != UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
+ Info.iLineLgplDisclaimer + 1);
+ }
+
+ if (!pSettings->fExternalCopyright)
+ {
+ if (Info.iLineCopyright == UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
+ if (Info.iLineLicense == UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
+ }
+ else if (Info.iLineCopyright != UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND,
+ "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
+ Info.iLineCopyright + 1);
+
+
+ if (RT_SUCCESS(pState->rc))
+ {
+ /*
+ * Do we need to make any changes?
+ */
+ bool fUpdateCopyright = !pSettings->fExternalCopyright
+ && ( !Info.fWellFormedCopyright
+ || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
+ bool fUpdateLicense = !pSettings->fExternalCopyright
+ && Info.enmLicenceOpt != kScmLicense_LeaveAlone
+ && ( !Info.fWellFormedLicense
+ || !Info.fIsCorrectLicense);
+ if ( fUpdateCopyright
+ || fUpdateLicense
+ || fAddLgplDisclaimer)
+ {
+ Assert(Info.iLineComment != UINT32_MAX);
+ Assert(Info.cLinesComment > 0);
+
+ /*
+ * Okay, do the work.
+ */
+ ScmStreamRewindForReading(pIn);
+
+ if (pSettings->fUpdateCopyrightYear)
+ Info.uLastYear = g_uYear;
+
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ if ( iLine == Info.iLineComment
+ && (fUpdateCopyright || fUpdateLicense) )
+ {
+ /* Leading blank line. */
+ ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
+ g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
+
+ /* Contributed by someone? */
+ if (Info.pszContributedBy)
+ {
+ const char *psz = Info.pszContributedBy;
+ for (;;)
+ {
+ const char *pszEol = strchr(psz, '\n');
+ size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
+ ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
+ g_aCopyrightCommentPrefix[enmCommentStyle].cch);
+ ScmStreamWrite(pOut, psz, cchContribLine);
+ ScmStreamPutEol(pOut, enmEol);
+ if (!pszEol)
+ break;
+ psz = pszEol + 1;
+ }
+
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
+ g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
+ }
+
+ /* Write the copyright comment line. */
+ ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
+ g_aCopyrightCommentPrefix[enmCommentStyle].cch);
+
+ char szCopyright[256];
+ size_t cchCopyright;
+ if (Info.uFirstYear == Info.uLastYear)
+ cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
+ Info.uFirstYear, g_szCopyrightHolder);
+ else
+ cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
+ Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
+
+ ScmStreamWrite(pOut, szCopyright, cchCopyright);
+ ScmStreamPutEol(pOut, enmEol);
+
+ if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
+ {
+ /* Blank line separating the two. */
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
+ g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
+
+ /* Write the license text. */
+ scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
+ enmCommentStyle, enmEol);
+
+ /* Final comment line. */
+ if (!Info.fExternalLicense)
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
+ g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
+ }
+ else
+ Assert(Info.fExternalLicense);
+
+ /* Skip the copyright and license text in the input file. */
+ rc = ScmStreamGetStatus(pOut);
+ if (RT_SUCCESS(rc))
+ {
+ iLine = Info.iLineComment + Info.cLinesComment;
+ rc = ScmStreamSeekByLine(pIn, iLine);
+ }
+ }
+ /*
+ * Add LGPL disclaimer?
+ */
+ else if ( iLine == Info.iLineAfterLgplComment
+ && fAddLgplDisclaimer)
+ {
+ ScmStreamPutEol(pOut, enmEol);
+ ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
+ g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
+ scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
+ enmCommentStyle, enmEol);
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
+ g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
+
+ /* put the actual line */
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ iLine++;
+ }
+ else
+ {
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ iLine++;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(Info.pszContributedBy);
+ return false;
+ }
+ } /* for each source line */
+
+ RTStrFree(Info.pszContributedBy);
+ return true;
+ }
+ }
+ }
+ else
+ ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
+ NOREF(pState); NOREF(pOut);
+ RTStrFree(Info.pszContributedBy);
+ return false;
+}
+
+
+/** Copyright updater for C-style comments. */
+bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
+}
+
+/** Copyright updater for hash-prefixed comments. */
+bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
+}
+
+/** Copyright updater for REM-prefixed comments. */
+bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
+}
+
+/** Copyright updater for python comments. */
+bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
+}
+
+/** Copyright updater for semicolon-prefixed comments. */
+bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
+}
+
+/** Copyright updater for sql comments. */
+bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
+}
+
+/** Copyright updater for tick-prefixed comments. */
+bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
+}
+
+
+/**
+ * Makefile.kup are empty files, enforce this.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pOut, pSettings);
+
+ /* These files should be zero bytes. */
+ if (pIn->cb == 0)
+ return false;
+ ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
+ return true;
+}
+
+/**
+ * Rewrite a kBuild makefile.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @todo
+ *
+ * Ideas for Makefile.kmk and Config.kmk:
+ * - sort if1of/ifn1of sets.
+ * - line continuation slashes should only be preceded by one space.
+ */
+bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF4(pState, pIn, pOut, pSettings);
+ return false;
+}
+
+
+static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
+ const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
+{
+ *ppchText = NULL;
+ *pcchText = 0;
+ *pfNeedFixing = false;
+
+ /*
+ * The first line.
+ */
+ if (pchLine[0] != '/')
+ return false;
+ size_t offLine = 1;
+ while (offLine < cchLine && pchLine[offLine] == '*')
+ offLine++;
+ if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
+ return false;
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+ if (offLine != cchLine)
+ return false;
+
+ size_t const cchBox = cchLine;
+ *pfNeedFixing = cchBox != cchWidth;
+
+ /*
+ * The next line, extracting the text.
+ */
+ SCMEOL enmEol;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (cchLine < cchBox - 3)
+ return false;
+
+ offLine = 0;
+ if (RT_C_IS_BLANK(pchLine[0]))
+ {
+ *pfNeedFixing = true;
+ offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
+ }
+
+ if (pchLine[offLine] != '*')
+ return false;
+ offLine++;
+
+ if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
+ return false;
+ offLine++;
+
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+ if (offLine >= cchLine)
+ return false;
+ if (!RT_C_IS_UPPER(pchLine[offLine]))
+ return false;
+
+ if (offLine != 4 || cchLine != cchBox)
+ *pfNeedFixing = true;
+
+ *ppchText = &pchLine[offLine];
+ size_t const offText = offLine;
+
+ /* From the end now. */
+ offLine = cchLine - 1;
+ while (RT_C_IS_BLANK(pchLine[offLine]))
+ offLine--;
+
+ if (pchLine[offLine] != '*')
+ return false;
+ offLine--;
+ if (!RT_C_IS_BLANK(pchLine[offLine]))
+ return false;
+ offLine--;
+ while (RT_C_IS_BLANK(pchLine[offLine]))
+ offLine--;
+ *pcchText = offLine - offText + 1;
+
+ /*
+ * Third line closes the box.
+ */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (cchLine < cchBox - 3)
+ return false;
+
+ offLine = 0;
+ if (RT_C_IS_BLANK(pchLine[0]))
+ {
+ *pfNeedFixing = true;
+ offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
+ }
+ while (offLine < cchLine && pchLine[offLine] == '*')
+ offLine++;
+ if (offLine < cchBox - 4)
+ return false;
+
+ if (pchLine[offLine] != '/')
+ return false;
+ offLine++;
+
+ if (offLine != cchBox)
+ *pfNeedFixing = true;
+
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+ if (offLine != cchLine)
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Flower box marker comments in C and C++ code.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixFlowerBoxMarkers)
+ return false;
+
+ /*
+ * Work thru the file line by line looking for flower box markers.
+ */
+ size_t cChanges = 0;
+ size_t cBlankLines = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Get a likely match for a first line.
+ */
+ if ( pchLine[0] == '/'
+ && cchLine > 20
+ && pchLine[1] == '*'
+ && pchLine[2] == '*'
+ && pchLine[3] == '*')
+ {
+ size_t const offSaved = ScmStreamTell(pIn);
+ char const *pchText;
+ size_t cchText;
+ bool fNeedFixing;
+ bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
+ &pchText, &cchText, &fNeedFixing);
+ if ( fIsFlowerBoxSection
+ && ( fNeedFixing
+ || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
+ {
+ while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
+ {
+ ScmStreamPutEol(pOut, enmEol);
+ cBlankLines++;
+ }
+
+ ScmStreamPutCh(pOut, '/');
+ ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
+ ScmStreamPutEol(pOut, enmEol);
+
+ static const char s_szLead[] = "* ";
+ ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
+ ScmStreamWrite(pOut, pchText, cchText);
+ size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
+ ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
+ ScmStreamPutCh(pOut, '*');
+ ScmStreamPutEol(pOut, enmEol);
+
+ ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
+ ScmStreamPutCh(pOut, '/');
+ ScmStreamPutEol(pOut, enmEol);
+
+ cChanges++;
+ cBlankLines = 0;
+ continue;
+ }
+
+ int rc = ScmStreamSeekAbsolute(pIn, offSaved);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+
+ /* Do blank line accounting so we can ensure at least two blank lines
+ before each section marker. */
+ if (!isBlankLine(pchLine, cchLine))
+ cBlankLines = 0;
+ else
+ cBlankLines++;
+ }
+ if (cChanges > 0)
+ ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
+ return cChanges != 0;
+}
+
+
+/**
+ * Looks for the start of a todo comment.
+ *
+ * @returns Offset into the line of the comment start sequence.
+ * @param pchLine The line to search.
+ * @param cchLineBeforeTodo The length of the line before the todo.
+ * @param pfSameLine Indicates whether it's refering to a statemtn on
+ * the same line comment (true), or the next
+ * statement (false).
+ */
+static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
+{
+ *pfSameLine = false;
+
+ /* Skip one '@' or '\\'. */
+ char ch;
+ if ( cchLineBeforeTodo > 2
+ && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
+ || ch == '\\' ) )
+ cchLineBeforeTodo--;
+
+ /* Skip blanks. */
+ while ( cchLineBeforeTodo > 2
+ && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
+ cchLineBeforeTodo--;
+
+ /* Look for same line indicator. */
+ if ( cchLineBeforeTodo > 0
+ && pchLine[cchLineBeforeTodo - 1] == '<')
+ {
+ *pfSameLine = true;
+ cchLineBeforeTodo--;
+ }
+
+ /* Skip *s */
+ while ( cchLineBeforeTodo > 1
+ && pchLine[cchLineBeforeTodo - 1] == '*')
+ cchLineBeforeTodo--;
+
+ /* Do we have a comment opening sequence. */
+ if ( cchLineBeforeTodo > 0
+ && pchLine[cchLineBeforeTodo - 1] == '/'
+ && ( ( cchLineBeforeTodo >= 2
+ && pchLine[cchLineBeforeTodo - 2] == '/')
+ || pchLine[cchLineBeforeTodo] == '*'))
+ {
+ /* Skip slashes at the start. */
+ while ( cchLineBeforeTodo > 0
+ && pchLine[cchLineBeforeTodo - 1] == '/')
+ cchLineBeforeTodo--;
+
+ return cchLineBeforeTodo;
+ }
+
+ return ~(size_t)0;
+}
+
+
+/**
+ * Looks for a TODO or todo in the given line.
+ *
+ * @returns Offset into the line of found, ~(size_t)0 if not.
+ * @param pchLine The line to search.
+ * @param cchLine The length of the line.
+ */
+static size_t findTodo(char const *pchLine, size_t cchLine)
+{
+ if (cchLine >= 4 + 2)
+ {
+ /* We don't search the first to chars because we need the start of a comment.
+ Also, skip the last three chars since we need at least four for a match. */
+ size_t const cchLineT = cchLine - 3;
+ if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
+ || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
+ {
+ for (size_t off = 2; off < cchLineT; off++)
+ {
+ char ch = pchLine[off];
+ if ( ( ch != 't'
+ && ch != 'T')
+ || ( (ch = pchLine[off + 1]) != 'o'
+ && ch != 'O')
+ || ( (ch = pchLine[off + 2]) != 'd'
+ && ch != 'D')
+ || ( (ch = pchLine[off + 3]) != 'o'
+ && ch != 'O')
+ || ( off + 4 != cchLine
+ && (ch = pchLine[off + 4]) != ' '
+ && ch != '\t'
+ && ch != ':' /** @todo */
+ && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
+ ) )
+ { /* not a hit - likely */ }
+ else
+ return off;
+ }
+ }
+ }
+ return ~(size_t)0;
+}
+
+
+/**
+ * Doxygen todos in C and C++ code.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixTodos)
+ return false;
+
+ /*
+ * Work thru the file line by line looking for the start of todo comments.
+ */
+ size_t cChanges = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Look for the word 'todo' in the line. We're currently only trying
+ * to catch comments starting with the word todo and adjust the start of
+ * the doxygen statement.
+ */
+ size_t offTodo = findTodo(pchLine, cchLine);
+ if ( offTodo != ~(size_t)0
+ && offTodo >= 2)
+ {
+ /* Work backwards to find the start of the comment. */
+ bool fSameLine = false;
+ size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
+ if (offCommentStart != ~(size_t)0)
+ {
+ char szNew[64];
+ size_t cchNew = 0;
+ szNew[cchNew++] = '/';
+ szNew[cchNew++] = pchLine[offCommentStart + 1];
+ szNew[cchNew++] = pchLine[offCommentStart + 1];
+ if (fSameLine)
+ szNew[cchNew++] = '<';
+ szNew[cchNew++] = ' ';
+ szNew[cchNew++] = '@';
+ szNew[cchNew++] = 't';
+ szNew[cchNew++] = 'o';
+ szNew[cchNew++] = 'd';
+ szNew[cchNew++] = 'o';
+
+ /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
+ but need to take into account that we might be at the end of the line before
+ adding the space. */
+ size_t offTodoAfter = offTodo + 4;
+ if ( offTodoAfter < cchLine
+ && pchLine[offTodoAfter] == ':')
+ offTodoAfter++;
+ if ( offTodoAfter < cchLine
+ && RT_C_IS_BLANK(pchLine[offTodoAfter]))
+ offTodoAfter++;
+ if (offTodoAfter < cchLine)
+ szNew[cchNew++] = ' ';
+
+ /* Write it out. */
+ ScmStreamWrite(pOut, pchLine, offCommentStart);
+ ScmStreamWrite(pOut, szNew, cchNew);
+ if (offTodoAfter < cchLine)
+ ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
+ ScmStreamPutEol(pOut, enmEol);
+
+ /* Check whether we actually made any changes. */
+ if ( cchNew != offTodoAfter - offCommentStart
+ || memcmp(szNew, &pchLine[offCommentStart], cchNew))
+ cChanges++;
+ continue;
+ }
+ }
+
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ if (cChanges > 0)
+ ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
+ return cChanges != 0;
+}
+
+
+/**
+ * Tries to parse a C/C++ preprocessor include directive.
+ *
+ * This is resonably forgiving and expects sane input.
+ *
+ * @retval kScmIncludeDir_Invalid if not a valid include directive.
+ * @retval kScmIncludeDir_Quoted
+ * @retval kScmIncludeDir_Bracketed
+ * @retval kScmIncludeDir_Macro
+ *
+ * @param pState The rewriter state (for repording malformed
+ * directives).
+ * @param pchLine The line to try parse as an include statement.
+ * @param cchLine The line length.
+ * @param ppchFilename Where to return the pointer to the filename part.
+ * @param pcchFilename Where to return the length of the filename.
+ */
+SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
+ const char **ppchFilename, size_t *pcchFilename)
+{
+ /* Skip leading spaces: */
+ while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
+ cchLine--, pchLine++;
+
+ /* Check for '#': */
+ if (cchLine > 0 && *pchLine == '#')
+ {
+ cchLine--;
+ pchLine++;
+
+ /* Skip spaces after '#' (optional): */
+ while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
+ cchLine--, pchLine++;
+
+ /* Check for 'include': */
+ static char const s_szInclude[] = "include";
+ if ( cchLine >= sizeof(s_szInclude)
+ && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
+ {
+ cchLine -= sizeof(s_szInclude) - 1;
+ pchLine += sizeof(s_szInclude) - 1;
+
+ /* Skip spaces after 'include' word (optional): */
+ while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
+ cchLine--, pchLine++;
+ if (cchLine > 0)
+ {
+ /* Quoted or bracketed? */
+ char const chFirst = *pchLine;
+ if (chFirst == '"' || chFirst == '<')
+ {
+ cchLine--;
+ pchLine++;
+ const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
+ if (pchEnd)
+ {
+ if (ppchFilename)
+ *ppchFilename = pchLine;
+ if (pcchFilename)
+ *pcchFilename = pchEnd - pchLine;
+ return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
+ }
+ ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
+ chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
+ }
+ /* C prepreprocessor macro? */
+ else if (ScmIsCIdentifierLeadChar(chFirst))
+ {
+ size_t cchFilename = 1;
+ while ( cchFilename < cchLine
+ && ScmIsCIdentifierChar(pchLine[cchFilename]))
+ cchFilename++;
+ if (ppchFilename)
+ *ppchFilename = pchLine;
+ if (pcchFilename)
+ *pcchFilename = cchFilename;
+ return kScmIncludeDir_Macro;
+ }
+ else
+ ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
+ }
+ else
+ ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
+ }
+ }
+
+ if (ppchFilename)
+ *ppchFilename = NULL;
+ if (pcchFilename)
+ *pcchFilename = 0;
+ return kScmIncludeDir_Invalid;
+}
+
+
+/**
+ * Fix err.h/errcore.h usage.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixErrH)
+ return false;
+
+ static struct
+ {
+ const char *pszHeader;
+ unsigned cchHeader;
+ int iLevel;
+ } const s_aHeaders[] =
+ {
+ { RT_STR_TUPLE("iprt/errcore.h"), 1 },
+ { RT_STR_TUPLE("iprt/err.h"), 2 },
+ { RT_STR_TUPLE("VBox/err.h"), 3 },
+ };
+ static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
+ {
+ { RT_STR_TUPLE("VINF_SUCCESS") },
+ { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
+ { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
+ { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
+ { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
+ { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
+ { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
+ { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
+ { RT_STR_TUPLE("VERR_INVALID_POINTER") },
+ { RT_STR_TUPLE("VERR_NO_MEMORY") },
+ { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
+ { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
+ { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
+ { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
+ { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
+ { RT_STR_TUPLE("VERR_WRONG_ORDER") },
+ { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
+ { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
+ { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
+ { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
+ { RT_STR_TUPLE("VERR_INTERRUPTED") },
+ { RT_STR_TUPLE("VINF_INTERRUPTED") },
+ { RT_STR_TUPLE("VERR_TIMEOUT") },
+ { RT_STR_TUPLE("VINF_TIMEOUT") },
+ { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
+ { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
+ { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
+ { RT_STR_TUPLE("VERR_TRY_AGAIN") },
+ { RT_STR_TUPLE("VINF_TRY_AGAIN") },
+ { RT_STR_TUPLE("VERR_PARSE_ERROR") },
+ { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
+ { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
+ { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
+ { RT_STR_TUPLE("VERR_CANCELLED") },
+ { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
+ { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
+ { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
+ { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
+ { RT_STR_TUPLE("VERR_NOT_FOUND") },
+ { RT_STR_TUPLE("VWRN_NOT_FOUND") },
+ { RT_STR_TUPLE("VERR_INVALID_STATE") },
+ { RT_STR_TUPLE("VWRN_INVALID_STATE") },
+ { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
+ { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
+ { RT_STR_TUPLE("VERR_END_OF_STRING") },
+ { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
+ { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
+ { RT_STR_TUPLE("VERR_DUPLICATE") },
+ { RT_STR_TUPLE("VERR_MISSING") },
+ { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
+ { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
+ { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
+ { RT_STR_TUPLE("VERR_MISMATCH") },
+ { RT_STR_TUPLE("VERR_WRONG_TYPE") },
+ { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
+ { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
+ { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
+ { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
+ { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
+ { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
+ { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
+ { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
+ { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
+ };
+
+ /*
+ * First pass: Scout #include err.h/errcore.h locations and usage.
+ *
+ * Note! This isn't entirely optimal since it's also parsing comments and
+ * strings, not just code. However it does a decent job for now.
+ */
+ int iIncludeLevel = 0;
+ int iUsageLevel = 0;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ iLine++;
+ if (cchLine < 6)
+ continue;
+
+ /*
+ * Look for #includes.
+ */
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ const char *pchFilename;
+ size_t cchFilename;
+ SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
+ if ( enmIncDir == kScmIncludeDir_Bracketed
+ || enmIncDir == kScmIncludeDir_Quoted)
+ {
+ unsigned i = RT_ELEMENTS(s_aHeaders);
+ while (i-- > 0)
+ if ( s_aHeaders[i].cchHeader == cchFilename
+ && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
+ {
+ if (iIncludeLevel < s_aHeaders[i].iLevel)
+ iIncludeLevel = s_aHeaders[i].iLevel;
+ break;
+ }
+
+ /* Special hack for error info. */
+ if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
+ iUsageLevel = 4;
+
+ /* Special hack for code templates. */
+ if ( cchFilename >= sizeof(".cpp.h")
+ && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
+ iUsageLevel = 4;
+ continue;
+ }
+ }
+ /*
+ * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
+ */
+ const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
+ if (pchHit)
+ {
+ const char *pchLeft = pchLine;
+ size_t cchLeft = cchLine;
+ do
+ {
+ size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
+ if (cchLeftHit < 6)
+ break;
+ if ( pchHit[4] == '_'
+ && ( pchHit == pchLine
+ || !ScmIsCIdentifierChar(pchHit[-1]))
+ && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
+ || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
+ || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
+ {
+ size_t cchIdentifier = 5;
+ while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
+ cchIdentifier++;
+ ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
+ iLine, pchHit - pchLine, cchIdentifier, pchHit);
+
+ if (iUsageLevel <= 1)
+ {
+ iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
+ if ( cchIdentifier == g_aLevel1Statuses[i].cch
+ && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
+ {
+ iUsageLevel = 1;
+ break;
+ }
+ }
+
+ pchLeft = pchHit + cchIdentifier;
+ cchLeft = cchLeftHit - cchIdentifier;
+ }
+ else
+ {
+ pchLeft = pchHit + 1;
+ cchLeft = cchLeftHit - 1;
+ }
+ pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
+ } while (pchHit != NULL);
+ }
+ }
+ ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
+
+ /*
+ * Second pass: Change err.h to errcore.h if we detected a need for change.
+ */
+ if ( iIncludeLevel <= iUsageLevel
+ || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
+ return false;
+
+ unsigned cChanges = 0;
+ ScmStreamRewindForReading(pIn);
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Look for #includes to modify.
+ */
+ if (cchLine >= 6)
+ {
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ const char *pchFilename;
+ size_t cchFilename;
+ SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
+ if ( enmIncDir == kScmIncludeDir_Bracketed
+ || enmIncDir == kScmIncludeDir_Quoted)
+ {
+ unsigned i = RT_ELEMENTS(s_aHeaders);
+ while (i-- > 0)
+ if ( s_aHeaders[i].cchHeader == cchFilename
+ && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
+ {
+ ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
+ size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
+ if (cchTrailing > 0)
+ ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
+ ScmStreamPutEol(pOut, enmEol);
+ cChanges++;
+ pchLine = NULL;
+ break;
+ }
+ if (!pchLine)
+ continue;
+ }
+ }
+ }
+
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
+ return true;
+}
+
+typedef struct
+{
+ const char *pch;
+ uint8_t cch;
+ uint8_t cchSpaces; /**< Number of expected spaces before the word. */
+ bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
+ bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
+} SCMMATCHWORD;
+
+
+int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
+ size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+
+ size_t offLine = 0;
+ for (size_t i = 0; i < cWords; i++)
+ {
+ SCMMATCHWORD const *pWord = &paWords[i];
+
+ /*
+ * Deal with spaces preceeding the word first:
+ */
+ if (pWord->fSpacesBefore)
+ {
+ size_t cchSpaces = 0;
+ size_t cchTabs = 0;
+ while (offLine < cchLine)
+ {
+ const char ch = pchLine[offLine];
+ if (ch == ' ')
+ cchSpaces++;
+ else if (ch == '\t')
+ cchTabs++;
+ else
+ break;
+ offLine++;
+ }
+
+ if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
+ { /* likely */ }
+ else if (cchSpaces == 0 && cchTabs == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
+ else
+ rc = VWRN_TRAILING_SPACES;
+ }
+ else
+ Assert(pWord->cchSpaces == 0);
+
+ /*
+ * C/C++ identifier?
+ */
+ if (pWord->fIdentifier)
+ {
+ if (offLine >= cchLine)
+ return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
+ "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
+ pWord->cch, pWord->pch, offLine);
+ if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
+ return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
+ pWord->cch, pWord->pch, offLine);
+ size_t const offStart = offLine++;
+ while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
+ offLine++;
+ if (paIdentifiers)
+ {
+ paIdentifiers->cch = offLine - offStart;
+ paIdentifiers->psz = &pchLine[offStart];
+ paIdentifiers++;
+ }
+ }
+ /*
+ * Match the exact word.
+ */
+ else if ( pWord->cch == 0
+ || ( pWord->cch <= cchLine - offLine
+ && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
+ offLine += pWord->cch;
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
+ }
+
+ /*
+ * Check for trailing characters/whatnot.
+ */
+ if (poffNext)
+ *poffNext = offLine;
+ else if (offLine != cchLine)
+ rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
+ return rc;
+}
+
+
+/**
+ * Fix header file include guards and \#pragma once.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixHeaderGuards)
+ return false;
+
+ /* always skip .cpp.h files */
+ size_t cchFilename = strlen(pState->pszFilename);
+ if ( cchFilename > sizeof(".cpp.h")
+ && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
+ return false;
+
+ RTERRINFOSTATIC ErrInfo;
+ char szNormalized[168];
+ size_t cchNormalized = 0;
+ int rc;
+ bool fRet = false;
+
+ /*
+ * Calculate the expected guard for this file, if so tasked.
+ * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
+ */
+ szNormalized[0] = '\0';
+ if (pSettings->pszGuardRelativeToDir)
+ {
+ rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
+ cchNormalized = strlen(szNormalized);
+ if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
+ rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
+ RTPathFilename(pState->pszFilename));
+ else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
+ {
+ const char *pszSrc = RTPathFilename(pState->pszFilename);
+ if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
+ return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
+ pszSrc -= 2;
+ while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
+ && !RTPATH_IS_SLASH(pszSrc[-1])
+ && !RTPATH_IS_VOLSEP(pszSrc[-1]))
+ pszSrc--;
+ rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
+ }
+ else
+ rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
+ pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
+ char ch;
+ while ((ch = szNormalized[cchNormalized]) != '\0')
+ {
+ if (!ScmIsCIdentifierChar(ch))
+ szNormalized[cchNormalized] = '_';
+ cchNormalized++;
+ }
+ }
+
+ /*
+ * First part looks for the #ifndef xxxx paired with #define xxxx.
+ *
+ * We blindly assume the first preprocessor directive in the file is the guard
+ * and will be upset if this isn't the case.
+ */
+ RTSTRTUPLE Guard = { NULL, 0 };
+ uint32_t cBlankLines = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ for (;;)
+ {
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (pchLine == NULL)
+ return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
+ if (cchLine >= 2)
+ {
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ /* #ifndef xxxx */
+ static const SCMMATCHWORD s_aIfndefGuard[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("ifndef"), 0, true, false },
+ { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
+ NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
+ ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
+ fRet |= rc != VINF_SUCCESS;
+ ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
+ ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
+
+ /* #define xxxx */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
+ ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
+ const SCMMATCHWORD aDefineGuard[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("define"), 0, true, false },
+ { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
+ NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
+ ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
+ ErrInfo.Core.pszMsg, cchLine, pchLine);
+ fRet |= rc != VINF_SUCCESS;
+
+ if (Guard.cch >= sizeof(szNormalized))
+ return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
+ ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
+
+ if (szNormalized[0] != '\0')
+ {
+ if ( Guard.cch != cchNormalized
+ || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
+ {
+ ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
+ ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
+ Guard.cch, Guard.psz, pState->pszFilename);
+ fRet = true;
+ }
+ Guard.psz = szNormalized;
+ Guard.cch = cchNormalized;
+ }
+
+ /*
+ * Write guard, making sure we've got a single blank line preceeding it.
+ */
+ ScmStreamPutEol(pOut, enmEol);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
+ ScmStreamWrite(pOut, Guard.psz, Guard.cch);
+ ScmStreamPutEol(pOut, enmEol);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
+ ScmStreamWrite(pOut, Guard.psz, Guard.cch);
+ rc = ScmStreamPutEol(pOut, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ break;
+ }
+ }
+
+ if (!isBlankLine(pchLine, cchLine))
+ {
+ while (cBlankLines-- > 0)
+ ScmStreamPutEol(pOut, enmEol);
+ cBlankLines = 0;
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ else
+ cBlankLines++;
+ }
+
+ /*
+ * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
+ */
+ size_t const iPragmaOnce = ScmStreamTellLine(pIn);
+ static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("ifndef"), 0, true, false },
+ { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ static const SCMMATCHWORD s_aPragmaOnce[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("pragma"), 1, true, false },
+ { RT_STR_TUPLE("once"), 1, true, false},
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ static const SCMMATCHWORD s_aEndif[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("endif"), 0, true, false },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+
+ /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
+ size_t offNext;
+ rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ fRet |= rc != VINF_SUCCESS;
+ if (offNext != cchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
+ iPragmaOnce + 1, cchLine, pchLine);
+
+ /* # pragma once */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
+ iPragmaOnce + 2);
+ rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
+ NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ fRet |= rc != VINF_SUCCESS;
+ else
+ return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
+ iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
+
+ /* #endif */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
+ iPragmaOnce + 3);
+ rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
+ NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ fRet |= rc != VINF_SUCCESS;
+ else
+ return ScmError(pState, rc,
+ "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
+ iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
+ ScmVerbose(pState, 3, "Found pragma once\n");
+ fRet |= !pSettings->fPragmaOnce;
+ }
+ else
+ {
+ rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "seek error\n");
+ fRet |= pSettings->fPragmaOnce;
+ ScmVerbose(pState, 2, "Missing #pragma once\n");
+ }
+
+ /*
+ * Write the pragma once stuff.
+ */
+ if (pSettings->fPragmaOnce)
+ {
+ ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
+ ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
+ rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+
+ /*
+ * Copy the rest of the file and remove pragma once statements, while
+ * looking for the last #endif in the file.
+ */
+ size_t iEndIfIn = 0;
+ size_t iEndIfOut = 0;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ if (cchLine > 2)
+ {
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ size_t off = pchHash - pchLine + 1;
+ while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
+ off++;
+ /* #pragma once */
+ if ( off + sizeof("pragma") - 1 <= cchLine
+ && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
+ {
+ rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ fRet = true;
+ continue;
+ }
+ }
+ /* #endif */
+ else if ( off + sizeof("endif") - 1 <= cchLine
+ && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
+ {
+ iEndIfIn = ScmStreamTellLine(pIn) - 1;
+ iEndIfOut = ScmStreamTellLine(pOut);
+ }
+ }
+ }
+
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+
+ /*
+ * Check out the last endif, making sure it's well formed and make sure it has the
+ * right kind of comment following it.
+ */
+ if (pSettings->fFixHeaderGuardEndif)
+ {
+ if (iEndIfOut == 0)
+ return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
+ rc = ScmStreamSeekByLine(pIn, iEndIfIn);
+ if (RT_FAILURE(rc))
+ return false;
+ rc = ScmStreamSeekByLine(pOut, iEndIfOut);
+ if (RT_FAILURE(rc))
+ return false;
+
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
+
+ char szTmp[64 + sizeof(szNormalized)];
+ size_t cchTmp;
+ if (pSettings->fEndifGuardComment)
+ cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
+ else
+ cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
+ fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
+ rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+
+ /* Copy out the remaining lines (assumes no #pragma once here). */
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ }
+
+ return fRet;
+}
+
+
+/**
+ * Rewrite a C/C++ source or header file.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @todo
+ *
+ * Ideas for C/C++:
+ * - space after if, while, for, switch
+ * - spaces in for (i=0;i<x;i++)
+ * - complex conditional, bird style.
+ * - remove unnecessary parentheses.
+ * - sort defined RT_OS_*|| and RT_ARCH
+ * - sizeof without parenthesis.
+ * - defined without parenthesis.
+ * - trailing spaces.
+ * - parameter indentation.
+ * - space after comma.
+ * - while (x--); -> multi line + comment.
+ * - else statement;
+ * - space between function and left parenthesis.
+ * - TODO, XXX, @todo cleanup.
+ * - Space before/after '*'.
+ * - ensure new line at end of file.
+ * - Indentation of precompiler statements (#ifdef, #defines).
+ * - space between functions.
+ * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
+ */
+bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+
+ RT_NOREF4(pState, pIn, pOut, pSettings);
+ return false;
+}
+