Flutter Engine
 
Loading...
Searching...
No Matches
catalog.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <algorithm>
8#include <fstream>
9#include <vector>
10
11#include "third_party/abseil-cpp/absl/log/log.h"
12#include "third_party/abseil-cpp/absl/strings/str_cat.h"
13
14namespace fs = std::filesystem;
15
16namespace {
17bool Overlaps(std::string_view a, std::string_view b) {
18 const char* const start1 = a.data();
19 const char* const end1 = start1 + a.size();
20 const char* const start2 = b.data();
21 const char* const end2 = start2 + b.size();
22
23 return start1 < end2 && start2 < end1;
24}
25
26bool EndsWith(std::string_view str, std::string_view suffix) {
27 if (suffix.length() > str.length()) {
28 return false;
29 }
30 return str.substr(str.length() - suffix.length()) == suffix;
31}
32
33std::string IgnoreWhitespace(std::string_view input) {
34 bool in_whitespace = false;
35 std::string result = "";
36 for (size_t i = 0; i < input.size(); ++i) {
37 char current = input[i];
38 if (std::isspace(current)) {
39 if (!in_whitespace) {
40 result.append("\\s+");
41 }
42 in_whitespace = true;
43 } else {
44 result.push_back(current);
45 in_whitespace = false;
46 }
47 }
48 if (EndsWith(result, "\\s+")) {
49 result.erase(result.end() - 3, result.end());
50 }
51 return result;
52}
53
54std::optional<Catalog::Match> FindMatchForSelectedMatcher(
55 std::string_view query,
56 RE2* matcher,
57 std::string_view matcher_name) {
58 int num_groups = matcher->NumberOfCapturingGroups();
59
60 if (num_groups == 0) {
61 std::string_view match_text;
62 if (matcher->Match(query, 0, query.length(), RE2::Anchor::UNANCHORED,
63 &match_text,
64 /*nsubmatch=*/1)) {
65 return Catalog::Match::MakeWithView(matcher_name, match_text);
66 }
67 } else {
68 // This will extract all non-grouped text from a match.
69 std::vector<re2::StringPiece> submatches(num_groups + 1);
70 if (matcher->Match(query, 0, query.length(), RE2::Anchor::UNANCHORED,
71 submatches.data(), num_groups + 1)) {
72 std::string_view full_match = submatches[0];
73 const char* full_match_end = full_match.data() + full_match.size();
74
75 std::string non_group_text;
76 non_group_text.reserve(full_match.size());
77 const char* position = full_match.data();
78 for (int i = 1; i <= num_groups; ++i) {
79 std::string_view submatch = submatches[i];
80 if (submatch.data() > position) {
81 non_group_text.append(position, submatch.data() - position);
82 }
83 position = submatch.data() + submatch.size();
84 }
85 if (position < full_match_end) {
86 non_group_text.append(position, full_match_end - position);
87 }
88
89 return Catalog::Match::MakeWithString(matcher_name,
90 std::move(non_group_text));
91 }
92 }
93
94 return std::nullopt;
95}
96} // namespace
97
98absl::StatusOr<Catalog> Catalog::Open(std::string_view data_dir) {
99 fs::path data_dir_path(data_dir);
100 if (!fs::exists(data_dir_path)) {
101 return absl::InvalidArgumentError(
102 absl::StrCat("Data directory doesn't exist ", data_dir));
103 }
104 fs::path licenses_path = data_dir_path / "licenses";
105 if (!fs::exists(licenses_path)) {
106 return absl::InvalidArgumentError(absl::StrCat(
107 "Licenses directory doesn't exist ", licenses_path.string()));
108 }
109
110 RE2::Set selector(RE2::Options(), RE2::Anchor::UNANCHORED);
111 std::vector<std::unique_ptr<RE2>> matchers;
112 std::vector<std::string> names;
113
114 for (const fs::path& file : fs::directory_iterator(licenses_path)) {
115 std::ifstream infile(file.string());
116 if (!infile.good()) {
117 return absl::InvalidArgumentError("Unable to open file " + file.string());
118 }
119
120 absl::StatusOr<Entry> entry = ParseEntry(infile);
121 if (!entry.ok()) {
122 return absl::InvalidArgumentError(
123 absl::StrCat("Unable to parse data entry at ", file.string(), " : ",
124 entry.status()));
125 }
126
127 std::string err;
128 selector.Add(entry->unique, &err);
129 if (!err.empty()) {
130 return absl::InvalidArgumentError(absl::StrCat(
131 "Unable to add unique key from ", file.string(), " : ", err));
132 }
133 names.emplace_back(std::move(entry->name));
134
135 auto matcher_re2 = std::make_unique<RE2>(entry->matcher);
136 if (!matcher_re2) {
137 return absl::InvalidArgumentError("Unable to make matcher.");
138 }
139
140 matchers.emplace_back(std::move(matcher_re2));
141 }
142
143 bool did_compile = selector.Compile();
144 if (!did_compile) {
145 return absl::UnknownError("Unable to compile selector.");
146 }
147
148 return Catalog(std::move(selector), std::move(matchers), std::move(names));
149}
150
151absl::StatusOr<Catalog> Catalog::Make(const std::vector<Entry>& entries) {
152 RE2::Set selector(RE2::Options(), RE2::Anchor::UNANCHORED);
153 std::vector<std::unique_ptr<RE2>> matchers;
154 std::vector<std::string> names;
155
156 for (const Entry& entry : entries) {
157 std::string err;
158 names.push_back(std::string(entry.name));
159 int idx = selector.Add(entry.unique, &err);
160 if (idx < 0) {
161 return absl::InvalidArgumentError(
162 absl::StrCat("Unable to add set entry: ", entry.unique, " ", err));
163 }
164 matchers.push_back(std::make_unique<RE2>(entry.matcher));
165 }
166
167 bool did_compile = selector.Compile();
168 if (!did_compile) {
169 return absl::OutOfRangeError("RE2::Set ran out of memory.");
170 }
171 return Catalog(std::move(selector), std::move(matchers), std::move(names));
172}
173
174Catalog::Catalog(RE2::Set selector,
175 std::vector<std::unique_ptr<RE2>> matchers,
176 std::vector<std::string> names)
177 : selector_(std::move(selector)),
178 matchers_(std::move(matchers)),
179 names_(std::move(names)) {}
180
181namespace {} // namespace
182
183absl::StatusOr<std::vector<Catalog::Match>> Catalog::FindMatch(
184 std::string_view query) const {
185 std::vector<int> selector_results;
186 if (!selector_.Match(query, &selector_results)) {
187 return absl::NotFoundError("Selector didn't match.");
188 }
189
190 std::vector<Catalog::Match> results;
191 std::vector<int> missed_results;
192 missed_results.reserve(selector_results.size());
193 std::vector<int> hit_results;
194 hit_results.reserve(selector_results.size());
195 for (int selector_result : selector_results) {
196 RE2* matcher = matchers_[selector_result].get();
197 std::optional<Match> match =
198 FindMatchForSelectedMatcher(query, matcher, names_[selector_result]);
199 if (match.has_value()) {
200 results.emplace_back(std::move(match.value()));
201 hit_results.push_back(selector_result);
202 } else {
203 missed_results.push_back(selector_result);
204 }
205 }
206 if (selector_results.size() != results.size()) {
207 std::stringstream missed;
208 for (size_t i = 0; i < missed_results.size(); ++i) {
209 if (i != 0) {
210 missed << ", ";
211 }
212 missed << names_[missed_results[i]];
213 }
214 std::stringstream hit;
215 hit << " Hit matcher(s): (";
216 for (size_t i = 0; i < hit_results.size(); ++i) {
217 if (i != 0) {
218 hit << ", ";
219 }
220 hit << names_[hit_results[i]];
221 }
222 hit << ")";
223 return absl::NotFoundError(
224 absl::StrCat("Selected matcher(s) (", missed.str(), ") didn't match.",
225 hit_results.empty() ? "" : hit.str()));
226 } else {
227 for (size_t i = 0; i < results.size(); ++i) {
228 for (size_t j = i + 1; j < results.size(); ++j) {
229 if (Overlaps(results[i].GetMatchedText(),
230 results[j].GetMatchedText())) {
231 return absl::InvalidArgumentError(absl::StrCat(
232 "Selected matchers overlap (", results[i].GetMatcher(), ", ",
233 results[j].GetMatcher(), ").\n", results[i].GetMatchedText(),
234 "\n############\n", results[j].GetMatchedText()));
235 }
236 }
237 }
238
239 return results;
240 }
241}
242
243absl::StatusOr<Catalog::Entry> Catalog::ParseEntry(std::istream& is) {
244 if (!is.good()) {
245 return absl::InvalidArgumentError("Bad stream.");
246 }
247 std::string name;
248 std::getline(is, name);
249 if (is.eof()) {
250 return absl::InvalidArgumentError("Bad stream.");
251 }
252 std::string unique;
253 std::getline(is, unique);
254 if (is.eof()) {
255 return absl::InvalidArgumentError("Bad stream.");
256 }
257
258 std::string matcher_text((std::istreambuf_iterator<char>(is)),
259 std::istreambuf_iterator<char>());
260
261 std::string ignore_whitespace_matcher = IgnoreWhitespace(matcher_text);
262
263 VLOG(4) << "matcher:" << name << ":\n" << ignore_whitespace_matcher;
264
265 return Catalog::Entry{.name = std::move(name),
266 .unique = std::move(unique),
267 .matcher = std::move(ignore_whitespace_matcher)};
268}
static Match MakeWithView(std::string_view matcher, std::string_view matched_text)
Definition catalog.h:37
static Match MakeWithString(std::string_view matcher, std::string matched_text)
Definition catalog.h:32
static absl::StatusOr< Entry > ParseEntry(std::istream &is)
VisibleForTesting.
Definition catalog.cc:243
static absl::StatusOr< Catalog > Open(std::string_view data_dir)
Definition catalog.cc:98
absl::StatusOr< std::vector< Match > > FindMatch(std::string_view query) const
Tries to identify a match for the query across the Catalog.
Definition catalog.cc:183
static absl::StatusOr< Catalog > Make(const std::vector< Entry > &entries)
Make a Catalog for testing.
Definition catalog.cc:151
static int input(yyscan_t yyscanner)
const char * name
Definition fuchsia.cc:49
Definition ref_ptr.h:261
VisibleForTesting.
Definition catalog.h:24
std::string name
Definition catalog.h:25