mirror of
https://github.com/cemu-project/vcpkg.git
synced 2025-02-22 18:47:09 +01:00
improve logic expression evaluation (#7508)
* better logic expression evaluation Improve the logic expression evaluation currently used when filtering dependencies. Biggest improvements: + Allow '|' operator + Support nested '()' + Allow whitespace + Useful error message for malformed expressions Also changed names of types to RawParagraph when that is what the original author was using.
This commit is contained in:
parent
4d551ff4b3
commit
22e0b9f376
@ -2,4 +2,4 @@ Source: pango
|
||||
Version: 1.40.11-4
|
||||
Homepage: https://ftp.gnome.org/pub/GNOME/sources/pango/
|
||||
Description: Text and font handling library.
|
||||
Build-Depends: glib, gettext, cairo, fontconfig, freetype, harfbuzz[glib] (!windows-static)
|
||||
Build-Depends: glib, gettext, cairo, fontconfig, freetype, harfbuzz[glib] (!(windows&static))
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <vcpkg/packagespec.h>
|
||||
#include <vcpkg/sourceparagraph.h>
|
||||
#include <vcpkg/parse.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@ -13,7 +14,7 @@ namespace vcpkg
|
||||
struct BinaryParagraph
|
||||
{
|
||||
BinaryParagraph();
|
||||
explicit BinaryParagraph(std::unordered_map<std::string, std::string> fields);
|
||||
explicit BinaryParagraph(Parse::RawParagraph fields);
|
||||
BinaryParagraph(const SourceParagraph& spgh, const Triplet& triplet, const std::string& abi_tag);
|
||||
BinaryParagraph(const SourceParagraph& spgh, const FeatureParagraph& fpgh, const Triplet& triplet);
|
||||
|
||||
|
10
toolsrc/include/vcpkg/logicexpression.h
Normal file
10
toolsrc/include/vcpkg/logicexpression.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace vcpkg
|
||||
{
|
||||
// Evaluate simple vcpkg logic expressions. An identifier in the expression is considered 'true'
|
||||
// if it is a substring of the evaluation_context (typically the name of the triplet)
|
||||
bool evaluate_expression(const std::string& expression, const std::string& evaluation_context);
|
||||
}
|
@ -12,7 +12,6 @@ namespace vcpkg::Paragraphs
|
||||
|
||||
Expected<RawParagraph> get_single_paragraph(const Files::Filesystem& fs, const fs::path& control_path);
|
||||
Expected<std::vector<RawParagraph>> get_paragraphs(const Files::Filesystem& fs, const fs::path& control_path);
|
||||
Expected<RawParagraph> parse_single_paragraph(const std::string& str);
|
||||
Expected<std::vector<RawParagraph>> parse_paragraphs(const std::string& str);
|
||||
|
||||
Parse::ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& control_path);
|
||||
|
@ -30,7 +30,7 @@ namespace vcpkg
|
||||
struct StatusParagraph
|
||||
{
|
||||
StatusParagraph() noexcept;
|
||||
explicit StatusParagraph(std::unordered_map<std::string, std::string>&& fields);
|
||||
explicit StatusParagraph(Parse::RawParagraph&& fields);
|
||||
|
||||
bool is_installed() const { return want == Want::INSTALL && state == InstallState::INSTALLED; }
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace vcpkg
|
||||
|
||||
BinaryParagraph::BinaryParagraph() = default;
|
||||
|
||||
BinaryParagraph::BinaryParagraph(std::unordered_map<std::string, std::string> fields)
|
||||
BinaryParagraph::BinaryParagraph(Parse::RawParagraph fields)
|
||||
{
|
||||
using namespace vcpkg::Parse;
|
||||
|
||||
|
@ -938,7 +938,7 @@ namespace vcpkg::Build
|
||||
Commands::Version::version());
|
||||
}
|
||||
|
||||
static BuildInfo inner_create_buildinfo(std::unordered_map<std::string, std::string> pgh)
|
||||
static BuildInfo inner_create_buildinfo(Parse::RawParagraph pgh)
|
||||
{
|
||||
Parse::ParagraphParser parser(std::move(pgh));
|
||||
|
||||
@ -995,7 +995,7 @@ namespace vcpkg::Build
|
||||
|
||||
BuildInfo read_build_info(const Files::Filesystem& fs, const fs::path& filepath)
|
||||
{
|
||||
const Expected<std::unordered_map<std::string, std::string>> pghs =
|
||||
const Expected<Parse::RawParagraph> pghs =
|
||||
Paragraphs::get_single_paragraph(fs, filepath);
|
||||
Checks::check_exit(VCPKG_LINE_INFO, pghs.get() != nullptr, "Invalid BUILD_INFO file for package");
|
||||
return inner_create_buildinfo(*pghs.get());
|
||||
|
@ -14,7 +14,7 @@ namespace vcpkg::Commands::Cache
|
||||
std::vector<BinaryParagraph> output;
|
||||
for (auto&& path : paths.get_filesystem().get_files_non_recursive(paths.packages))
|
||||
{
|
||||
const Expected<std::unordered_map<std::string, std::string>> pghs =
|
||||
const Expected<Parse::RawParagraph> pghs =
|
||||
Paragraphs::get_single_paragraph(paths.get_filesystem(), path / "CONTROL");
|
||||
if (const auto p = pghs.get())
|
||||
{
|
||||
|
@ -108,7 +108,7 @@ namespace vcpkg::Commands::Import
|
||||
const fs::path include_directory(args.command_arguments[1]);
|
||||
const fs::path project_directory(args.command_arguments[2]);
|
||||
|
||||
const Expected<std::unordered_map<std::string, std::string>> pghs =
|
||||
const Expected<Parse::RawParagraph> pghs =
|
||||
Paragraphs::get_single_paragraph(paths.get_filesystem(), control_file_path);
|
||||
Checks::check_exit(VCPKG_LINE_INFO,
|
||||
pghs.get() != nullptr,
|
||||
|
285
toolsrc/src/vcpkg/logicexpression.cpp
Normal file
285
toolsrc/src/vcpkg/logicexpression.cpp
Normal file
@ -0,0 +1,285 @@
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <vcpkg/logicexpression.h>
|
||||
#include <vcpkg/base/checks.h>
|
||||
#include <vcpkg/base/system.print.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace vcpkg
|
||||
{
|
||||
struct ParseError
|
||||
{
|
||||
ParseError(int column, std::string line, std::string message)
|
||||
:column(column), line(line), message(message)
|
||||
{}
|
||||
|
||||
const int column;
|
||||
const std::string line;
|
||||
const std::string message;
|
||||
|
||||
void print_error() const
|
||||
{
|
||||
System::print2(System::Color::error,
|
||||
"Error: ", message, "\n"
|
||||
" on expression: \"", line, "\"\n",
|
||||
" ", std::string(column, ' '), "^\n");
|
||||
Checks::exit_fail(VCPKG_LINE_INFO);
|
||||
}
|
||||
};
|
||||
|
||||
// logic expression supports the following :
|
||||
// primary-expression:
|
||||
// ( logic-expression )
|
||||
// identifier
|
||||
// identifier:
|
||||
// alpha-numeric string of characters
|
||||
// logic-expression: <- this is the entry point
|
||||
// not-expression
|
||||
// not-expression | logic-expression
|
||||
// not-expression & logic-expression
|
||||
// not-expression:
|
||||
// ! primary-expression
|
||||
// primary-expression
|
||||
//
|
||||
// | and & have equal precidence and cannot be used together at the same nesting level
|
||||
// for example a|b&c is not allowd but (a|b)&c and a|(b&c) are allowed.
|
||||
class ExpressionParser
|
||||
{
|
||||
public:
|
||||
ExpressionParser(const std::string& str, const std::string& evaluation_context)
|
||||
: raw_text(str), evaluation_context(evaluation_context)
|
||||
{
|
||||
go_to_begin();
|
||||
|
||||
final_result = logic_expression();
|
||||
|
||||
if (current_iter != raw_text.end())
|
||||
{
|
||||
add_error("Invalid logic expression");
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
err->print_error();
|
||||
final_result = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get_result() const
|
||||
{
|
||||
return final_result;
|
||||
}
|
||||
|
||||
bool has_error() const
|
||||
{
|
||||
return err == nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool final_result;
|
||||
|
||||
std::string::const_iterator current_iter;
|
||||
const std::string& raw_text;
|
||||
char current_char;
|
||||
|
||||
const std::string& evaluation_context;
|
||||
|
||||
std::unique_ptr<ParseError> err;
|
||||
|
||||
void add_error(std::string message, int column = -1)
|
||||
{
|
||||
// avoid castcading errors by only saving the first
|
||||
if (!err)
|
||||
{
|
||||
if (column < 0)
|
||||
{
|
||||
column = current_column();
|
||||
}
|
||||
err = std::make_unique<ParseError>(column, raw_text, message);
|
||||
}
|
||||
|
||||
// Avoid error loops by skipping to the end
|
||||
skip_to_end();
|
||||
}
|
||||
|
||||
int current_column() const
|
||||
{
|
||||
return static_cast<int>(current_iter - raw_text.begin());
|
||||
}
|
||||
|
||||
void go_to_begin()
|
||||
{
|
||||
current_iter = raw_text.begin();
|
||||
current_char = (current_iter != raw_text.end() ? *current_iter : current_char);
|
||||
|
||||
if (current_char == ' ' || current_char == '\t')
|
||||
{
|
||||
next_skip_whitespace();
|
||||
}
|
||||
}
|
||||
void skip_to_end()
|
||||
{
|
||||
current_iter = raw_text.end();
|
||||
current_char = '\0';
|
||||
}
|
||||
char current() const
|
||||
{
|
||||
return current_char;
|
||||
}
|
||||
char next()
|
||||
{
|
||||
if (current_char != '\0')
|
||||
{
|
||||
current_iter++;
|
||||
current_char = (current_iter != raw_text.end() ? *current_iter : '\0');
|
||||
}
|
||||
return current();
|
||||
}
|
||||
void skip_whitespace()
|
||||
{
|
||||
while (current_char == ' ' || current_char == '\t')
|
||||
{
|
||||
current_char = next();
|
||||
}
|
||||
}
|
||||
char next_skip_whitespace()
|
||||
{
|
||||
next();
|
||||
skip_whitespace();
|
||||
return current_char;
|
||||
}
|
||||
|
||||
static bool is_alphanum(char ch)
|
||||
{
|
||||
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-');
|
||||
}
|
||||
|
||||
bool evaluate_identifier(const std::string name) const
|
||||
{
|
||||
return evaluation_context.find(name) != std::string::npos;
|
||||
}
|
||||
|
||||
// identifier:
|
||||
// alpha-numeric string of characters
|
||||
bool identifier_expression()
|
||||
{
|
||||
auto curr = current();
|
||||
std::string name;
|
||||
|
||||
for (curr = current(); is_alphanum(curr); curr = next())
|
||||
{
|
||||
name += curr;
|
||||
}
|
||||
|
||||
if (name.empty())
|
||||
{
|
||||
add_error("Invalid logic expression, unexpected character");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = evaluate_identifier(name);
|
||||
skip_whitespace();
|
||||
return result;
|
||||
}
|
||||
|
||||
// not-expression:
|
||||
// ! primary-expression
|
||||
// primary-expression
|
||||
bool not_expression()
|
||||
{
|
||||
if (current() == '!')
|
||||
{
|
||||
next_skip_whitespace();
|
||||
return !primary_expression();
|
||||
}
|
||||
|
||||
return primary_expression();
|
||||
}
|
||||
|
||||
|
||||
template <char oper, char other, bool operation(bool, bool)>
|
||||
bool logic_expression_helper(bool seed)
|
||||
{
|
||||
do
|
||||
{
|
||||
// Support chains of the operator to avoid breaking backwards compatability
|
||||
while (next() == oper) {};
|
||||
seed = operation(not_expression(), seed);
|
||||
|
||||
} while (current() == oper);
|
||||
|
||||
if (current() == other)
|
||||
{
|
||||
add_error("Mixing & and | is not allowed, Use () to specify order of operations.");
|
||||
}
|
||||
|
||||
skip_whitespace();
|
||||
return seed;
|
||||
}
|
||||
static bool and_helper(bool left, bool right)
|
||||
{
|
||||
return left && right;
|
||||
}
|
||||
static bool or_helper(bool left, bool right)
|
||||
{
|
||||
return left || right;
|
||||
}
|
||||
|
||||
// logic-expression: <- entry point
|
||||
// not-expression
|
||||
// not-expression | logic-expression
|
||||
// not-expression & logic-expression
|
||||
bool logic_expression()
|
||||
{
|
||||
auto result = not_expression();
|
||||
|
||||
switch (current())
|
||||
{
|
||||
case '|':
|
||||
{
|
||||
return logic_expression_helper< '|', '&', or_helper > (result);
|
||||
}
|
||||
case '&':
|
||||
{
|
||||
return logic_expression_helper< '&', '|', and_helper > (result);
|
||||
}
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// primary-expression:
|
||||
// ( logic-expression )
|
||||
// identifier
|
||||
bool primary_expression()
|
||||
{
|
||||
if (current() == '(')
|
||||
{
|
||||
next_skip_whitespace();
|
||||
bool result = logic_expression();
|
||||
if (current() != ')')
|
||||
{
|
||||
add_error("Error: missing closing )");
|
||||
return result;
|
||||
}
|
||||
next_skip_whitespace();
|
||||
return result;
|
||||
}
|
||||
|
||||
return identifier_expression();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool evaluate_expression(const std::string& expression, const std::string& evaluation_context)
|
||||
{
|
||||
ExpressionParser parser(expression, evaluation_context);
|
||||
|
||||
return parser.get_result();
|
||||
}
|
||||
}
|
@ -116,7 +116,7 @@ namespace vcpkg::Paragraphs
|
||||
skip_spaces(ch);
|
||||
}
|
||||
|
||||
void get_paragraph(char& ch, std::unordered_map<std::string, std::string>& fields)
|
||||
void get_paragraph(char& ch, RawParagraph& fields)
|
||||
{
|
||||
fields.clear();
|
||||
std::string fieldname;
|
||||
@ -141,9 +141,9 @@ namespace vcpkg::Paragraphs
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::unordered_map<std::string, std::string>> get_paragraphs()
|
||||
std::vector<RawParagraph> get_paragraphs()
|
||||
{
|
||||
std::vector<std::unordered_map<std::string, std::string>> paragraphs;
|
||||
std::vector<RawParagraph> paragraphs;
|
||||
|
||||
char ch;
|
||||
peek(ch);
|
||||
@ -164,7 +164,20 @@ namespace vcpkg::Paragraphs
|
||||
}
|
||||
};
|
||||
|
||||
Expected<std::unordered_map<std::string, std::string>> get_single_paragraph(const Files::Filesystem& fs,
|
||||
Expected<RawParagraph> parse_single_paragraph(const std::string& str)
|
||||
{
|
||||
const std::vector<RawParagraph> p =
|
||||
Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs();
|
||||
|
||||
if (p.size() == 1)
|
||||
{
|
||||
return p.at(0);
|
||||
}
|
||||
|
||||
return std::error_code(ParagraphParseResult::EXPECTED_ONE_PARAGRAPH);
|
||||
}
|
||||
|
||||
Expected<RawParagraph> get_single_paragraph(const Files::Filesystem& fs,
|
||||
const fs::path& control_path)
|
||||
{
|
||||
const Expected<std::string> contents = fs.read_contents(control_path);
|
||||
@ -176,7 +189,7 @@ namespace vcpkg::Paragraphs
|
||||
return contents.error();
|
||||
}
|
||||
|
||||
Expected<std::vector<std::unordered_map<std::string, std::string>>> get_paragraphs(const Files::Filesystem& fs,
|
||||
Expected<std::vector<RawParagraph>> get_paragraphs(const Files::Filesystem& fs,
|
||||
const fs::path& control_path)
|
||||
{
|
||||
const Expected<std::string> contents = fs.read_contents(control_path);
|
||||
@ -188,27 +201,14 @@ namespace vcpkg::Paragraphs
|
||||
return contents.error();
|
||||
}
|
||||
|
||||
Expected<std::unordered_map<std::string, std::string>> parse_single_paragraph(const std::string& str)
|
||||
{
|
||||
const std::vector<std::unordered_map<std::string, std::string>> p =
|
||||
Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs();
|
||||
|
||||
if (p.size() == 1)
|
||||
{
|
||||
return p.at(0);
|
||||
}
|
||||
|
||||
return std::error_code(ParagraphParseResult::EXPECTED_ONE_PARAGRAPH);
|
||||
}
|
||||
|
||||
Expected<std::vector<std::unordered_map<std::string, std::string>>> parse_paragraphs(const std::string& str)
|
||||
Expected<std::vector<RawParagraph>> parse_paragraphs(const std::string& str)
|
||||
{
|
||||
return Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs();
|
||||
}
|
||||
|
||||
ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path)
|
||||
{
|
||||
Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = get_paragraphs(fs, path / "CONTROL");
|
||||
Expected<std::vector<RawParagraph>> pghs = get_paragraphs(fs, path / "CONTROL");
|
||||
if (auto vector_pghs = pghs.get())
|
||||
{
|
||||
return SourceControlFile::parse_control_file(std::move(*vector_pghs));
|
||||
@ -221,7 +221,7 @@ namespace vcpkg::Paragraphs
|
||||
|
||||
Expected<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec)
|
||||
{
|
||||
Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs =
|
||||
Expected<std::vector<RawParagraph>> pghs =
|
||||
get_paragraphs(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL");
|
||||
|
||||
if (auto p = pghs.get())
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
namespace vcpkg::Parse
|
||||
{
|
||||
static Optional<std::string> remove_field(std::unordered_map<std::string, std::string>* fields,
|
||||
static Optional<std::string> remove_field(RawParagraph* fields,
|
||||
const std::string& fieldname)
|
||||
{
|
||||
auto it = fields->find(fieldname);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <vcpkg/packagespec.h>
|
||||
#include <vcpkg/sourceparagraph.h>
|
||||
#include <vcpkg/triplet.h>
|
||||
#include <vcpkg/logicexpression.h>
|
||||
|
||||
#include <vcpkg/base/checks.h>
|
||||
#include <vcpkg/base/expected.h>
|
||||
@ -142,7 +143,7 @@ namespace vcpkg
|
||||
}
|
||||
|
||||
ParseExpected<SourceControlFile> SourceControlFile::parse_control_file(
|
||||
std::vector<std::unordered_map<std::string, std::string>>&& control_paragraphs)
|
||||
std::vector<Parse::RawParagraph>&& control_paragraphs)
|
||||
{
|
||||
if (control_paragraphs.size() == 0)
|
||||
{
|
||||
@ -222,15 +223,8 @@ namespace vcpkg
|
||||
std::vector<std::string> ret;
|
||||
for (auto&& dep : deps)
|
||||
{
|
||||
auto qualifiers = Strings::split(dep.qualifier, "&");
|
||||
if (std::all_of(qualifiers.begin(), qualifiers.end(), [&](const std::string& qualifier) {
|
||||
if (qualifier.empty()) return true;
|
||||
if (qualifier[0] == '!')
|
||||
{
|
||||
return t.canonical_name().find(qualifier.substr(1)) == std::string::npos;
|
||||
}
|
||||
return t.canonical_name().find(qualifier) != std::string::npos;
|
||||
}))
|
||||
const auto & qualifier = dep.qualifier;
|
||||
if (qualifier.empty() || evaluate_expression(qualifier, t.canonical_name()))
|
||||
{
|
||||
ret.emplace_back(dep.name());
|
||||
}
|
||||
@ -244,15 +238,8 @@ namespace vcpkg
|
||||
std::vector<Features> ret;
|
||||
for (auto&& dep : deps)
|
||||
{
|
||||
auto qualifiers = Strings::split(dep.qualifier, "&");
|
||||
if (std::all_of(qualifiers.begin(), qualifiers.end(), [&](const std::string& qualifier) {
|
||||
if (qualifier.empty()) return true;
|
||||
if (qualifier[0] == '!')
|
||||
{
|
||||
return t.canonical_name().find(qualifier.substr(1)) == std::string::npos;
|
||||
}
|
||||
return t.canonical_name().find(qualifier) != std::string::npos;
|
||||
}))
|
||||
const auto & qualifier = dep.qualifier;
|
||||
if (qualifier.empty() || evaluate_expression(qualifier, t.canonical_name()))
|
||||
{
|
||||
ret.emplace_back(dep.depend);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace vcpkg
|
||||
.push_back('\n');
|
||||
}
|
||||
|
||||
StatusParagraph::StatusParagraph(std::unordered_map<std::string, std::string>&& fields)
|
||||
StatusParagraph::StatusParagraph(Parse::RawParagraph&& fields)
|
||||
: want(Want::ERROR_STATE), state(InstallState::ERROR_STATE)
|
||||
{
|
||||
auto status_it = fields.find(BinaryParagraphRequiredField::STATUS);
|
||||
|
@ -51,7 +51,7 @@ namespace vcpkg
|
||||
{
|
||||
const auto& pghs = *p_pghs;
|
||||
|
||||
std::unordered_map<std::string, std::string> keys;
|
||||
Parse::RawParagraph keys;
|
||||
if (pghs.size() > 0) keys = pghs[0];
|
||||
|
||||
for (size_t x = 1; x < pghs.size(); ++x)
|
||||
|
@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
|
@ -249,6 +249,7 @@
|
||||
<ClCompile Include="..\src\vcpkg\help.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\input.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\install.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\logicexpression.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\metrics.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\packagespec.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\packagespecparseresult.cpp" />
|
||||
|
@ -108,6 +108,9 @@
|
||||
<ClCompile Include="..\src\vcpkg\install.cpp">
|
||||
<Filter>Source Files\vcpkg</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\vcpkg\logicexpression.cpp">
|
||||
<Filter>Source Files\vcpkg</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\vcpkg\metrics.cpp">
|
||||
<Filter>Source Files\vcpkg</Filter>
|
||||
</ClCompile>
|
||||
|
Loading…
x
Reference in New Issue
Block a user