mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
215 lines
6.8 KiB
C++
215 lines
6.8 KiB
C++
#include "console_core.h"
|
|
|
|
#include <chrono>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
namespace dingosdk {
|
|
namespace {
|
|
|
|
bool is_separator(char value) noexcept {
|
|
return value == ' ' || value == '\t';
|
|
}
|
|
|
|
bool is_allowed_input_character(unsigned char value) noexcept {
|
|
return value == static_cast<unsigned char>('\t') ||
|
|
(value >= static_cast<unsigned char>(' ') &&
|
|
value <= static_cast<unsigned char>('~'));
|
|
}
|
|
|
|
char ascii_lower(char value) noexcept {
|
|
if (value >= 'A' && value <= 'Z') {
|
|
return static_cast<char>(value + ('a' - 'A'));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CommandParseResult parse_console_command(std::string_view input) {
|
|
CommandParseResult result;
|
|
if (input.size() > console_max_command_bytes) {
|
|
result.error = CommandParseError::input_too_long;
|
|
result.error_offset = console_max_command_bytes;
|
|
return result;
|
|
}
|
|
|
|
for (std::size_t index = 0; index < input.size(); ++index) {
|
|
if (!is_allowed_input_character(static_cast<unsigned char>(input[index]))) {
|
|
result.error = CommandParseError::invalid_character;
|
|
result.error_offset = index;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
std::string argument;
|
|
argument.reserve(input.size());
|
|
bool argument_started = false;
|
|
bool escaped = false;
|
|
char quote = '\0';
|
|
|
|
const auto finish_argument = [&]() -> bool {
|
|
if (!argument_started) return true;
|
|
if (result.arguments.size() == console_max_arguments) {
|
|
result.error = CommandParseError::too_many_arguments;
|
|
return false;
|
|
}
|
|
result.arguments.push_back(argument);
|
|
argument.clear();
|
|
argument_started = false;
|
|
return true;
|
|
};
|
|
|
|
for (std::size_t index = 0; index < input.size(); ++index) {
|
|
const char value = input[index];
|
|
if (escaped) {
|
|
argument.push_back(value);
|
|
argument_started = true;
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
if (value == '\\') {
|
|
escaped = true;
|
|
argument_started = true;
|
|
continue;
|
|
}
|
|
if (quote != '\0') {
|
|
if (value == quote) {
|
|
quote = '\0';
|
|
} else {
|
|
argument.push_back(value);
|
|
}
|
|
argument_started = true;
|
|
continue;
|
|
}
|
|
if (value == '\'' || value == '"') {
|
|
quote = value;
|
|
argument_started = true;
|
|
continue;
|
|
}
|
|
if (is_separator(value)) {
|
|
if (!finish_argument()) {
|
|
result.error_offset = index;
|
|
result.arguments.clear();
|
|
return result;
|
|
}
|
|
continue;
|
|
}
|
|
argument.push_back(value);
|
|
argument_started = true;
|
|
}
|
|
|
|
if (escaped) {
|
|
result.error = CommandParseError::dangling_escape;
|
|
result.error_offset = input.empty() ? 0 : input.size() - 1;
|
|
result.arguments.clear();
|
|
return result;
|
|
}
|
|
if (quote != '\0') {
|
|
result.error = CommandParseError::unterminated_quote;
|
|
result.error_offset = input.size();
|
|
result.arguments.clear();
|
|
return result;
|
|
}
|
|
if (!finish_argument()) {
|
|
result.error_offset = input.size();
|
|
result.arguments.clear();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool ascii_case_insensitive_starts_with(std::string_view value,
|
|
std::string_view prefix) noexcept {
|
|
if (prefix.size() > value.size()) return false;
|
|
for (std::size_t index = 0; index < prefix.size(); ++index) {
|
|
if (ascii_lower(value[index]) != ascii_lower(prefix[index])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ConsoleLogBuffer::ConsoleLogBuffer(ConsoleLogLimits limits)
|
|
: limits_(limits) {
|
|
if (limits_.max_lines == 0 || limits_.max_bytes == 0 ||
|
|
limits_.max_line_bytes == 0 || limits_.max_line_bytes > limits_.max_bytes) {
|
|
throw std::invalid_argument("Invalid console log limits");
|
|
}
|
|
}
|
|
|
|
std::uint64_t console_timestamp_ms() noexcept {
|
|
static const auto start = std::chrono::steady_clock::now();
|
|
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::steady_clock::now() - start).count());
|
|
}
|
|
|
|
const char* console_source_name(ConsoleSource source) noexcept {
|
|
return logging::name(source).data();
|
|
}
|
|
|
|
std::string format_console_line(const ConsoleLogLine& line, bool timestamp) {
|
|
std::ostringstream output;
|
|
if (timestamp) output << '[' << std::fixed << std::setprecision(3)
|
|
<< std::setw(9) << static_cast<double>(line.elapsed_ms) / 1000.0 << "] ";
|
|
output << logging::name(line.context) << ": [" << console_source_name(line.source)
|
|
<< "] [" << logging::name(line.severity) << "] ";
|
|
output << line.text;
|
|
return output.str();
|
|
}
|
|
|
|
std::uint64_t ConsoleLogBuffer::append(std::string_view text,
|
|
ConsoleSeverity severity, ConsoleSource source, logging::Context context) {
|
|
std::lock_guard lock(mutex_);
|
|
if (text.empty()) return latest_sequence_;
|
|
const auto elapsed_ms = console_timestamp_ms();
|
|
|
|
std::size_t start = 0;
|
|
while (start < text.size()) {
|
|
const auto newline = text.find('\n', start);
|
|
if (newline == std::string_view::npos) {
|
|
append_line_locked(text.substr(start), elapsed_ms, severity, source, context);
|
|
break;
|
|
}
|
|
auto line = text.substr(start, newline - start);
|
|
if (!line.empty() && line.back() == '\r') line.remove_suffix(1);
|
|
append_line_locked(line, elapsed_ms, severity, source, context);
|
|
start = newline + 1;
|
|
}
|
|
return latest_sequence_;
|
|
}
|
|
|
|
std::vector<ConsoleLogLine> ConsoleLogBuffer::snapshot_after(
|
|
std::uint64_t after_sequence) const {
|
|
std::lock_guard lock(mutex_);
|
|
std::vector<ConsoleLogLine> snapshot;
|
|
for (const auto& line : lines_) {
|
|
if (line.sequence > after_sequence) snapshot.push_back(line);
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::uint64_t ConsoleLogBuffer::latest_sequence() const noexcept {
|
|
std::lock_guard lock(mutex_);
|
|
return latest_sequence_;
|
|
}
|
|
|
|
void ConsoleLogBuffer::append_line_locked(std::string_view text, std::uint64_t elapsed_ms,
|
|
ConsoleSeverity severity, ConsoleSource source, logging::Context context) {
|
|
if (latest_sequence_ == (std::numeric_limits<std::uint64_t>::max)()) {
|
|
throw std::overflow_error("Console log sequence exhausted");
|
|
}
|
|
const auto bounded = text.substr(0, limits_.max_line_bytes);
|
|
lines_.push_back({++latest_sequence_, std::string(bounded), elapsed_ms, severity, source, context});
|
|
stored_bytes_ += bounded.size();
|
|
enforce_limits_locked();
|
|
}
|
|
|
|
void ConsoleLogBuffer::enforce_limits_locked() {
|
|
while (lines_.size() > limits_.max_lines || stored_bytes_ > limits_.max_bytes) {
|
|
stored_bytes_ -= lines_.front().text.size();
|
|
lines_.pop_front();
|
|
}
|
|
}
|
|
|
|
} // namespace dingosdk
|