Changeset View
Changeset View
Standalone View
Standalone View
plugins/clazy/utils.cpp
- This file was added.
1 | /* This file is part of KDevelop | ||||
---|---|---|---|---|---|
2 | | ||||
3 | Copyright 2018 Anton Anikin <anton@anikin.xyz> | ||||
4 | | ||||
5 | This program is free software; you can redistribute it and/or | ||||
6 | modify it under the terms of the GNU General Public | ||||
7 | License as published by the Free Software Foundation; either | ||||
8 | version 2 of the License, or (at your option) any later version. | ||||
9 | | ||||
10 | This program is distributed in the hope that it will be useful, | ||||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
13 | General Public License for more details. | ||||
14 | | ||||
15 | You should have received a copy of the GNU General Public License | ||||
16 | along with this program; see the file COPYING. If not, write to | ||||
17 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
18 | Boston, MA 02110-1301, USA. | ||||
19 | */ | ||||
20 | | ||||
21 | #include "utils.h" | ||||
22 | | ||||
23 | #include <interfaces/icore.h> | ||||
24 | #include <interfaces/iproject.h> | ||||
25 | #include <interfaces/iprojectcontroller.h> | ||||
26 | #include <project/interfaces/ibuildsystemmanager.h> | ||||
27 | #include <project/projectmodel.h> | ||||
28 | | ||||
29 | #include <KLocalizedString> | ||||
30 | | ||||
31 | #include <QFile> | ||||
32 | #include <QJsonDocument> | ||||
33 | #include <QJsonArray> | ||||
34 | #include <QJsonObject> | ||||
35 | #include <QRegularExpression> | ||||
36 | | ||||
37 | namespace Clazy | ||||
38 | { | ||||
39 | | ||||
40 | QString prettyPathName(const QString& path) | ||||
41 | { | ||||
42 | return KDevelop::ICore::self()->projectController()->prettyFileName( | ||||
43 | QUrl::fromLocalFile(path), | ||||
44 | KDevelop::IProjectController::FormatPlain); | ||||
45 | } | ||||
46 | | ||||
47 | QStringList compileCommandsFiles(const QString& jsonFilePath, QString& error) | ||||
48 | { | ||||
49 | QStringList paths; | ||||
50 | | ||||
51 | QFile jsonFile(jsonFilePath); | ||||
52 | if (!jsonFile.open(QFile::ReadOnly | QFile::Text)) { | ||||
53 | error = i18n("Unable to open %1 for reading", jsonFilePath); | ||||
54 | return paths; | ||||
55 | } | ||||
56 | | ||||
57 | QJsonParseError jsonError; | ||||
58 | auto document = QJsonDocument::fromJson(jsonFile.readAll(), &jsonError); | ||||
59 | | ||||
60 | if (jsonError.error) { | ||||
61 | error = i18n("JSON error during parsing %1: %2", jsonFilePath, jsonError.errorString()); | ||||
62 | return paths; | ||||
63 | } | ||||
64 | | ||||
65 | if (!document.isArray()) { | ||||
66 | error = i18n("JSON error during parsing %1: document is not an array", jsonFilePath); | ||||
67 | return paths; | ||||
68 | } | ||||
69 | | ||||
70 | static const QString KEY_FILE = QStringLiteral("file"); | ||||
kossebau: static is not really needed here, there is not anything special about this over the other… | |||||
71 | | ||||
72 | const auto array = document.array(); | ||||
73 | for (const auto& value : array) { | ||||
74 | if (!value.isObject()) { | ||||
75 | continue; | ||||
76 | } | ||||
77 | | ||||
78 | const QJsonObject entry = value.toObject(); | ||||
79 | if (entry.contains(KEY_FILE)) { | ||||
80 | auto path = entry[KEY_FILE].toString(); | ||||
81 | if (QFile::exists(path)) | ||||
82 | { | ||||
83 | paths += path; | ||||
84 | } | ||||
85 | } | ||||
86 | } | ||||
87 | | ||||
88 | return paths; | ||||
89 | } | ||||
90 | | ||||
91 | // Very simple Markdown parser/converter. Does not provide full Markdown language support and | ||||
92 | // was tested only with Clazy documentation. | ||||
93 | class MarkdownConverter | ||||
94 | { | ||||
FWIW: Okular uses the Discount library for reading markdown documents, showing them as HTML. Also Cantor will do so too: D14738: Add the markdown entry. Maybe it'd be a good idea to use it -- there are few options:
pino: FWIW: Okular uses the [[http://www.pell.portland.or.us/~orc/Code/discount/ | Discount library]]… | |||||
95 | public: | ||||
96 | MarkdownConverter() | ||||
97 | { | ||||
98 | tagStart.resize(STATE_COUNT); | ||||
99 | tagEnd.resize(STATE_COUNT); | ||||
100 | | ||||
101 | tagStart[EMPTY] = tagEnd[EMPTY] = QString(); | ||||
102 | | ||||
103 | tagStart[HEADING] = QStringLiteral("<b>"); | ||||
104 | tagEnd [HEADING] = QStringLiteral("</b>"); | ||||
105 | | ||||
106 | tagStart[PARAGRAPH] = QStringLiteral("<p>"); | ||||
107 | tagEnd [PARAGRAPH] = QStringLiteral("</p>"); | ||||
108 | | ||||
109 | tagStart[PREFORMATTED] = QStringLiteral("<pre>"); | ||||
110 | tagEnd [PREFORMATTED] = QStringLiteral("</pre>"); | ||||
111 | | ||||
112 | tagStart[LIST] = QStringLiteral("<ul><li>"); | ||||
113 | tagEnd [LIST] = QStringLiteral("</li></ul>"); | ||||
114 | } | ||||
115 | | ||||
116 | ~MarkdownConverter() = default; | ||||
117 | | ||||
kossebau: = default | |||||
118 | QString toHtml(const QString& markdown) | ||||
119 | { | ||||
120 | const QRegularExpression hRE(QStringLiteral("(#+) (.+)")); | ||||
121 | QRegularExpressionMatch match; | ||||
122 | | ||||
123 | state = EMPTY; | ||||
124 | html.clear(); | ||||
125 | html += QStringLiteral("<html>"); | ||||
126 | | ||||
127 | auto lines = markdown.split('\n'); | ||||
128 | for (auto line : lines) { | ||||
129 | if (line.isEmpty()) { | ||||
130 | setState(EMPTY); | ||||
131 | continue; | ||||
132 | } | ||||
133 | | ||||
134 | if (line.startsWith("#")) { | ||||
135 | auto match = hRE.match(line); | ||||
136 | if (match.hasMatch()) { | ||||
137 | setState(HEADING); | ||||
138 | html += match.captured(2); | ||||
139 | setState(EMPTY); | ||||
140 | if (match.captured(1).size() == 1) { | ||||
141 | html += QStringLiteral("<hr>"); | ||||
142 | } | ||||
143 | } | ||||
144 | continue; | ||||
145 | } | ||||
146 | | ||||
147 | if (line.startsWith(QStringLiteral("```"))) { | ||||
148 | setState((state == PREFORMATTED) ? EMPTY : PREFORMATTED); | ||||
149 | continue; | ||||
150 | } | ||||
151 | | ||||
152 | if (line.startsWith(QStringLiteral(" "))) { | ||||
153 | if (state == EMPTY) { | ||||
154 | setState(PREFORMATTED); | ||||
155 | } | ||||
156 | } else if ( | ||||
157 | line.startsWith(QStringLiteral("- ")) || | ||||
158 | line.startsWith(QStringLiteral("* "))) { | ||||
159 | // force close and reopen list - this fixes cases when we don't have | ||||
160 | // separator line between items | ||||
161 | setState(EMPTY); | ||||
162 | setState(LIST); | ||||
163 | line = line.mid(2); | ||||
164 | } | ||||
165 | | ||||
166 | if (state == EMPTY) { | ||||
167 | setState(PARAGRAPH); | ||||
168 | } | ||||
169 | | ||||
170 | processLine(line); | ||||
171 | } | ||||
172 | setState(EMPTY); | ||||
173 | | ||||
174 | html += QStringLiteral("</html>"); | ||||
175 | return html.join('\n'); | ||||
176 | } | ||||
177 | | ||||
178 | private: | ||||
179 | enum STATE { | ||||
180 | EMPTY, | ||||
181 | HEADING, | ||||
182 | PARAGRAPH, | ||||
183 | PREFORMATTED, | ||||
184 | LIST, | ||||
185 | | ||||
186 | STATE_COUNT | ||||
187 | }; | ||||
188 | | ||||
189 | void setState(int newState) | ||||
190 | { | ||||
191 | if (state == newState) { | ||||
192 | return; | ||||
193 | } | ||||
194 | | ||||
195 | if (state != EMPTY) { | ||||
196 | html += tagEnd[state]; | ||||
197 | } | ||||
198 | | ||||
199 | if (newState != EMPTY) { | ||||
200 | html += tagStart[newState]; | ||||
201 | } | ||||
202 | | ||||
203 | state = newState; | ||||
204 | } | ||||
205 | | ||||
206 | void processLine(QString& line) | ||||
207 | { | ||||
208 | static const QRegularExpression ttRE(QStringLiteral("`([^`]+)`")); | ||||
209 | static const QRegularExpression bdRE(QStringLiteral("\\*\\*([^\\*]+)\\*\\*")); | ||||
210 | static const QRegularExpression itRE(QStringLiteral("[^\\*]\\*([^\\*]+)\\*[^\\*]")); | ||||
211 | | ||||
212 | static auto applyRE = [](const QRegularExpression& re, QString& line, const QString& tag) { | ||||
213 | auto i = re.globalMatch(line); | ||||
214 | while (i.hasNext()) { | ||||
215 | auto match = i.next(); | ||||
216 | line.replace(match.captured(0), QStringLiteral("<%1>%2</%1>").arg(tag, match.captured(1))); | ||||
217 | } | ||||
218 | }; | ||||
219 | | ||||
220 | if (state != PREFORMATTED) { | ||||
221 | line.replace(QLatin1Char('&'), QLatin1String("&")); | ||||
222 | line.replace(QLatin1Char('<'), QLatin1String("<")); | ||||
223 | line.replace(QLatin1Char('>'), QLatin1String(">")); | ||||
224 | | ||||
mini optimization: use QLatin1Char for single letter, using the replace(QChar, QLatin1String) overload kossebau: mini optimization: use QLatin1Char for single letter, using the `replace(QChar, QLatin1String)… | |||||
225 | line.replace(QLatin1Char('\"'), QLatin1String(""")); | ||||
226 | line.replace(QLatin1Char('\''), QLatin1String("'")); | ||||
227 | | ||||
228 | applyRE(ttRE, line, QStringLiteral("tt")); | ||||
229 | applyRE(bdRE, line, QStringLiteral("b")); | ||||
230 | applyRE(itRE, line, QStringLiteral("i")); | ||||
231 | } | ||||
232 | | ||||
233 | html += line; | ||||
234 | } | ||||
235 | | ||||
236 | private: | ||||
237 | int state; | ||||
238 | QVector<QString> tagStart; | ||||
239 | QVector<QString> tagEnd; | ||||
kossebau: split off data member with oen `private:` section | |||||
240 | QStringList html; | ||||
241 | }; | ||||
242 | | ||||
243 | QString markdown2html(const QByteArray& markdown) | ||||
244 | { | ||||
245 | MarkdownConverter converter; | ||||
246 | return converter.toHtml(markdown); | ||||
247 | } | ||||
248 | | ||||
249 | } |
static is not really needed here, there is not anything special about this over the other QStringLiterals?