-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscanner.cpp
More file actions
136 lines (126 loc) · 5.03 KB
/
scanner.cpp
File metadata and controls
136 lines (126 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/////////////////////////////////////////////////////////////////////////////
// Name: scanner.cpp
// Purpose: Search wxTranslation macros in source code files
// Author: Jan Buchholz
// Created: 2025-11-12
/////////////////////////////////////////////////////////////////////////////
#include "scanner.h"
#include <regex>
#include <sstream>
#include <fstream>
#include <iostream>
#include "appsettings.h"
Scanner::Scanner(const TranslationFile* tf) {
m_tf = tf;
m_projectFolder = m_tf->getSettings().project_folder;
m_exclusionList = splitAtNewline(m_tf->getSettings().exclude_folders);
AppSettings* appSettings = &AppSettings::instance();
m_extensions = appSettings->getAppSettings().extensions;
}
std::vector<std::string> Scanner::splitAtNewline(const std::string& input) {
std::vector<std::string> result{};
std::istringstream stream(input);
std::string line;
while (std::getline(stream, line)) result.push_back(line);
return result;
}
bool Scanner::wildcardMatch(const std::string& pattern, const std::string& target) {
std::string regex_pattern;
std::string p = pattern, t = target;
std::ranges::transform(p, p.begin(), ::tolower);
std::ranges::transform(t, t.begin(), ::tolower);
for (const char c : p) {
switch (c) {
case '*': regex_pattern += ".*";
break;
case '?': regex_pattern += ".";
break;
case '.': regex_pattern += "\\.";
break;
default: regex_pattern += c;
}
}
const std::regex re(regex_pattern);
return std::regex_search(t, re);
}
// Remove single and multi line comments
std::string Scanner::removeComments(const std::string& code) {
std::string no_comments = code;
// remove /* */ comments
// /\* = /*
// \*/ = */
// [\s\S] = any character (whitespace or not whitespace)
// *? = find as many, but non-greedy
no_comments = std::regex_replace(no_comments, std::regex(R"(/\*[\s\S]*?\*/)"), "");
return no_comments;
}
// Concatenate cohesive string literals
std::string Scanner::mergeStringLiterals(const std::string& code) {
// remove whitespace characters between string literals
// R"delim(...)delim") = delim begin and end of Raw string
// "(\s*)"\s* = regex expression, removes whitespace and '"' characters
// "text1 "\n"text2" => "text1 text2"
return std::regex_replace(code, std::regex(R"delim("(\s*)"\s*)delim"), "");
}
// Extract text to be translated from wx macro
std::vector<std::string> Scanner::extractTranslationStrings(const std::string& code) {
std::vector<std::string> entries{};
// match _() and wxTRANSLATE()
// Microsoft Copilot helped here :)
std::regex pattern("(?:_|wxTRANSLATE)\\s*\\(\\s*\"((?:[^\"\\\\]|\\\\.|[\\n\\r])*)\"\\s*\\)", std::regex::ECMAScript);
auto begin = std::sregex_iterator(code.begin(), code.end(), pattern);
std::sregex_iterator end;
for (auto it = begin; it != end; ++it) {
entries.push_back((*it)[1].str()); // extract captured group
}
return entries;
}
std::vector<std::string> Scanner::processFile(const std::filesystem::path& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) return {};
const std::string content((std::istreambuf_iterator(file)),
std::istreambuf_iterator<char>());
std::string clean = removeComments(content);
clean = mergeStringLiterals(clean);
return extractTranslationStrings(clean);
}
// Traverse directory
std::vector<mo_single_translation> Scanner::scanFolder() {
std::vector<mo_single_translation> translations{};
std::filesystem::recursive_directory_iterator dirIt(m_projectFolder), endIt;
while (dirIt != endIt) {
const std::filesystem::directory_entry& entry = *dirIt;
std::filesystem::path relativePath = std::filesystem::relative(entry.path(), m_projectFolder);
std::string entryName = relativePath.string();
if (entry.is_directory()) {
for (auto& ex : m_exclusionList) {
if (wildcardMatch(ex, entryName)) {
dirIt.disable_recursion_pending();
break;
}
}
} else if (entry.is_regular_file()) {
bool skip = false;
for (auto& ex : m_exclusionList) {
if (wildcardMatch(ex, entryName)) {
skip = true;
break;
}
}
if (!skip) {
auto ext = relativePath.extension();
for (auto& ex : m_extensions) {
if (ex.second && ex.first == ext) {
std::vector<std::string> trs = processFile(entry.path());
for (const auto& tr : trs) {
mo_single_translation s = {.source_file = entryName, .original = tr, .status = untranslated};
translations.push_back(s);
}
}
}
}
}
++dirIt;
}
return translations;
}