1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qdocgenerator.h" |
5 | |
6 | #include <QtCore/qdir.h> |
7 | |
8 | #include <iostream> |
9 | |
10 | using namespace Qt::Literals::StringLiterals; |
11 | |
12 | namespace QDocGenerator { |
13 | |
14 | // See definition of idstring and licenseid in https://spdx.org/spdx-specification-21-web-version |
15 | static bool isSpdxLicenseId(const QString &str) { |
16 | if (str.isEmpty()) |
17 | return false; |
18 | for (auto iter(str.cbegin()); iter != str.cend(); ++iter) { |
19 | const QChar c = *iter; |
20 | if (!((c >= QLatin1Char('A') && c <= QLatin1Char('Z')) |
21 | || (c >= QLatin1Char('a') && c <= QLatin1Char('z')) |
22 | || (c >= QLatin1Char('0') && c <= QLatin1Char('9')) |
23 | || (c == QLatin1Char('-')) || (c == QLatin1Char('.')))) |
24 | return false; |
25 | } |
26 | return true; |
27 | } |
28 | |
29 | static QString languageJoin(const QStringList &list) |
30 | { |
31 | QString result; |
32 | for (int i = 0; i < list.size(); ++i) { |
33 | QString delimiter = u", "_s ; |
34 | if (i == list.size() - 1) // last item |
35 | delimiter.clear(); |
36 | else if (list.size() == 2) |
37 | delimiter = u" and "_s ; |
38 | else if (list.size() > 2 && i == list.size() - 2) |
39 | delimiter = u", and "_s ; // oxford comma |
40 | result += list[i] + delimiter; |
41 | } |
42 | |
43 | return result; |
44 | } |
45 | |
46 | // Embed source code between \badcode ... \endbadcode |
47 | // Also, avoid '*/' breaking qdoc by passing the star as argument |
48 | static void sourceCode(QTextStream &out, const QString &src) |
49 | { |
50 | out << "\\badcode *\n" ; |
51 | out << QString(src).replace(before: u"*/"_s , after: u"\\1/"_s ); |
52 | out << "\n\\endcode\n\n" ; |
53 | } |
54 | |
55 | static void generate(QTextStream &out, const Package &package, const QDir &baseDir) |
56 | { |
57 | out << "/*!\n\n" ; |
58 | for (const QString &part : package.qtParts) { |
59 | out << "\\ingroup attributions-" << package.qdocModule << "-" << part << "\n" ; |
60 | out << "\\ingroup attributions-" << part << "\n" ; |
61 | } |
62 | |
63 | if (package.qtParts.contains(str: "libs"_L1 )) { |
64 | // show up in xxx-index.html page of module |
65 | out << "\\ingroup attributions-" << package.qdocModule << "\n" ; |
66 | // include in '\generatelist annotatedattributions' |
67 | out << "\\page " << package.qdocModule << "-attribution-" << package.id |
68 | << ".html\n" ; |
69 | out << "\\attribution\n" ; |
70 | } else { |
71 | out << "\\page " << package.qdocModule << "-attribution-" << package.id |
72 | << ".html \n" ; |
73 | } |
74 | |
75 | out << "\\target " << package.id << "\n\n" ; |
76 | out << "\\title " << package.name; |
77 | if (!package.version.isEmpty()) |
78 | out << ", version " << package.version; |
79 | out << "\n\n\\brief " << package.license << "\n\n" ; |
80 | |
81 | if (!package.description.isEmpty()) |
82 | out << package.description << "\n\n" ; |
83 | |
84 | if (!package.qtUsage.isEmpty()) |
85 | out << package.qtUsage << "\n\n" ; |
86 | |
87 | QStringList sourcePaths; |
88 | if (package.files.isEmpty()) { |
89 | sourcePaths << baseDir.relativeFilePath(fileName: package.path); |
90 | } else { |
91 | const QDir packageDir(package.path); |
92 | for (const QString &filePath: package.files) { |
93 | const QString absolutePath = packageDir.absoluteFilePath(fileName: filePath); |
94 | sourcePaths << baseDir.relativeFilePath(fileName: absolutePath); |
95 | } |
96 | } |
97 | |
98 | out << "The sources can be found in " << languageJoin(list: sourcePaths) << ".\n\n" ; |
99 | |
100 | const bool hasPackageVersion = !package.version.isEmpty(); |
101 | const bool hasPackageDownloadLocation = !package.downloadLocation.isEmpty(); |
102 | if (!package.homepage.isEmpty()) { |
103 | out << "\\l{" << package.homepage << "}{Project Homepage}" ; |
104 | if (hasPackageVersion) |
105 | out << ", " ; |
106 | } |
107 | if (hasPackageVersion) { |
108 | out << "upstream version: " ; |
109 | if (hasPackageDownloadLocation) |
110 | out << "\\l{" << package.downloadLocation << "}{" ; |
111 | out << package.version; |
112 | if (hasPackageDownloadLocation) |
113 | out << "}" ; |
114 | } |
115 | |
116 | out << "\n\n" ; |
117 | |
118 | QString copyright; |
119 | if (!package.copyright.isEmpty()) |
120 | copyright = package.copyright; |
121 | else if (!package.copyrightFileContents.isEmpty()) |
122 | copyright = package.copyrightFileContents; |
123 | |
124 | if (!copyright.isEmpty()) { |
125 | out << "\n" ; |
126 | sourceCode(out, src: copyright); |
127 | } |
128 | |
129 | if (isSpdxLicenseId(str: package.licenseId) && package.licenseId != "NONE"_L1 ) { |
130 | out << "\\l{https://spdx.org/licenses/" << package.licenseId << ".html}" |
131 | << "{" << package.license << "}.\n\n" ; |
132 | } else if (package.licenseId.startsWith(s: "urn:dje:license:"_L1 )) { |
133 | out << "\\l{https://enterprise.dejacode.com/licenses/public/" << package.licenseId.mid(position: 16) |
134 | << "/}{" << package.license << "}.\n\n" ; |
135 | } else { |
136 | out << package.license << ".\n\n" ; |
137 | } |
138 | |
139 | foreach (const QString &license, package.licenseFilesContents) |
140 | sourceCode(out, src: license); |
141 | |
142 | out << "*/\n" ; |
143 | } |
144 | |
145 | void generate(QTextStream &out, const QList<Package> &packages, const QString &baseDirectory, |
146 | LogLevel logLevel) |
147 | { |
148 | if (logLevel == VerboseLog) |
149 | std::cerr << qPrintable(tr("Generating qdoc file..." )) << std::endl; |
150 | |
151 | QDir baseDir(baseDirectory); |
152 | for (const Package &package : packages) |
153 | generate(out, package, baseDir); |
154 | } |
155 | |
156 | } // namespace QDocGenerator |
157 | |