♻️ refactor(cli): validate startup arguments
This commit is contained in:
@@ -8,18 +8,43 @@ import std;
|
||||
import lsp.core.server;
|
||||
import lsp.utils.args_parser;
|
||||
|
||||
namespace
|
||||
{
|
||||
void SetupLogger(const lsp::utils::ServerConfig& config)
|
||||
{
|
||||
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e [%t] [%^%l%$] %v");
|
||||
auto logger = config.log_file.empty() ? spdlog::stderr_logger_mt("console_logger") : spdlog::basic_logger_mt("file_logger", config.log_file);
|
||||
logger->set_level(config.log_level);
|
||||
spdlog::set_default_logger(logger);
|
||||
spdlog::set_level(config.log_level);
|
||||
}
|
||||
}
|
||||
|
||||
export int Run(int argc, char* argv[])
|
||||
{
|
||||
lsp::utils::ArgsParser& args_parser = lsp::utils::ArgsParser::Instance();
|
||||
auto& config = args_parser.Parse(argc, argv);
|
||||
|
||||
if (config.show_help)
|
||||
auto parsed = lsp::utils::ParseArgs(argc, argv);
|
||||
if (!parsed)
|
||||
{
|
||||
lsp::utils::ArgsParser::PrintHelp(argv[0]);
|
||||
std::cerr << "[TSL-LSP] Argument error: " << parsed.error() << '\n';
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (parsed->action == lsp::utils::ParseAction::kShowHelp)
|
||||
{
|
||||
lsp::utils::PrintHelp(std::cout, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lsp::utils::ArgsParser::SetupLogger(config);
|
||||
const auto& config = parsed->config;
|
||||
try
|
||||
{
|
||||
SetupLogger(config);
|
||||
}
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
std::cerr << "[TSL-LSP] Failed to initialize logger: " << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -27,16 +52,18 @@ export int Run(int argc, char* argv[])
|
||||
lsp::core::LspServer server(config.thread_count, config.interpreter_path);
|
||||
server.Run();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
std::cerr << "[TSL-LSP] Server fatal error: " << e.what() << std::endl;
|
||||
spdlog::error("Server fatal error: {}", e.what());
|
||||
std::cerr << "[TSL-LSP] Server fatal error: " << error.what() << '\n';
|
||||
spdlog::error("Server fatal error: {}", error.what());
|
||||
spdlog::shutdown();
|
||||
return 1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "[TSL-LSP] Server unknown fatal error" << std::endl;
|
||||
std::cerr << "[TSL-LSP] Server unknown fatal error\n";
|
||||
spdlog::error("Server unknown fatal error");
|
||||
spdlog::shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,141 +1,131 @@
|
||||
module;
|
||||
|
||||
export module lsp.utils.args_parser;
|
||||
import spdlog;
|
||||
|
||||
import spdlog;
|
||||
import std;
|
||||
|
||||
export namespace lsp::utils
|
||||
{
|
||||
struct ServerConfig
|
||||
{
|
||||
bool use_stderr = false;
|
||||
bool show_help = false;
|
||||
std::size_t thread_count = 4;
|
||||
spdlog::level::level_enum log_level = spdlog::level::info;
|
||||
std::string log_file;
|
||||
std::string interpreter_path;
|
||||
};
|
||||
|
||||
class ArgsParser
|
||||
enum class ParseAction
|
||||
{
|
||||
public:
|
||||
ArgsParser(const ArgsParser&) = delete;
|
||||
ArgsParser& operator=(const ArgsParser&) = delete;
|
||||
static ArgsParser& Instance();
|
||||
|
||||
const ServerConfig& Parse(int argc, char* argv[]);
|
||||
const ServerConfig& GetConfig() const;
|
||||
|
||||
static void SetupLogger(const ServerConfig& config);
|
||||
static void PrintHelp(const std::string& program_name);
|
||||
|
||||
private:
|
||||
ArgsParser() = default;
|
||||
~ArgsParser() = default;
|
||||
|
||||
ServerConfig config_;
|
||||
kRun,
|
||||
kShowHelp,
|
||||
};
|
||||
|
||||
struct ParseResult
|
||||
{
|
||||
ParseAction action = ParseAction::kRun;
|
||||
ServerConfig config;
|
||||
};
|
||||
|
||||
std::expected<ParseResult, std::string> ParseArgs(int argc, char* const argv[]);
|
||||
void PrintHelp(std::ostream& output, std::string_view program_name);
|
||||
}
|
||||
|
||||
namespace lsp::utils
|
||||
{
|
||||
ArgsParser& ArgsParser::Instance()
|
||||
namespace
|
||||
{
|
||||
static ArgsParser instance;
|
||||
return instance;
|
||||
constexpr std::size_t kMinThreadCount = 1;
|
||||
constexpr std::size_t kMaxThreadCount = 256;
|
||||
|
||||
std::optional<spdlog::level::level_enum> ParseLogLevel(std::string_view value)
|
||||
{
|
||||
if (value == "trace")
|
||||
return spdlog::level::trace;
|
||||
if (value == "debug")
|
||||
return spdlog::level::debug;
|
||||
if (value == "info")
|
||||
return spdlog::level::info;
|
||||
if (value == "warn")
|
||||
return spdlog::level::warn;
|
||||
if (value == "error")
|
||||
return spdlog::level::err;
|
||||
if (value == "off")
|
||||
return spdlog::level::off;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
const ServerConfig& ArgsParser::Parse(int argc, char* argv[])
|
||||
std::expected<ParseResult, std::string> ParseArgs(int argc, char* const argv[])
|
||||
{
|
||||
config_ = ServerConfig{};
|
||||
// Default to stderr so LSP stdio (stdout) stays clean.
|
||||
config_.use_stderr = true;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (std::string_view(argv[i]) == "--help")
|
||||
return ParseResult{ .action = ParseAction::kShowHelp, .config = {} };
|
||||
}
|
||||
|
||||
ParseResult result;
|
||||
constexpr std::string_view kLogPrefix = "--log=";
|
||||
constexpr std::string_view kLogFilePrefix = "--log-file=";
|
||||
constexpr std::string_view kThreadsPrefix = "--threads=";
|
||||
constexpr std::string_view kInterpreterPrefix = "--interpreter=";
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "--help")
|
||||
const std::string_view argument = argv[i];
|
||||
if (argument.starts_with(kLogPrefix))
|
||||
{
|
||||
config_.show_help = true;
|
||||
return config_;
|
||||
const auto value = argument.substr(kLogPrefix.size());
|
||||
auto level = ParseLogLevel(value);
|
||||
if (!level)
|
||||
return std::unexpected("Invalid value for --log: '" +
|
||||
std::string(value) + "'");
|
||||
result.config.log_level = *level;
|
||||
}
|
||||
|
||||
if (arg == "--log=trace")
|
||||
config_.log_level = spdlog::level::trace;
|
||||
else if (arg == "--log=debug")
|
||||
config_.log_level = spdlog::level::debug;
|
||||
else if (arg == "--log=info")
|
||||
config_.log_level = spdlog::level::info;
|
||||
else if (arg == "--log=warn")
|
||||
config_.log_level = spdlog::level::warn;
|
||||
else if (arg == "--log=error")
|
||||
config_.log_level = spdlog::level::err;
|
||||
else if (arg == "--log=off")
|
||||
config_.log_level = spdlog::level::off;
|
||||
else if (arg == "--log-stderr")
|
||||
config_.use_stderr = true;
|
||||
else if (arg == "--log-stdout")
|
||||
config_.use_stderr = false;
|
||||
else if (arg.starts_with(kLogFilePrefix))
|
||||
config_.log_file = arg.substr(kLogFilePrefix.size());
|
||||
else if (arg == "--use-stdio")
|
||||
config_.use_stderr = true;
|
||||
else if (arg.starts_with(kThreadsPrefix))
|
||||
else if (argument.starts_with(kLogFilePrefix))
|
||||
{
|
||||
auto value = arg.substr(kThreadsPrefix.size());
|
||||
config_.thread_count = std::max<std::size_t>(1, static_cast<std::size_t>(std::stoi(value)));
|
||||
const auto value = argument.substr(kLogFilePrefix.size());
|
||||
if (value.empty())
|
||||
return std::unexpected("--log-file requires a non-empty path");
|
||||
result.config.log_file = value;
|
||||
}
|
||||
else if (arg.starts_with(kInterpreterPrefix))
|
||||
else if (argument.starts_with(kThreadsPrefix))
|
||||
{
|
||||
config_.interpreter_path = arg.substr(kInterpreterPrefix.size());
|
||||
const auto value = argument.substr(kThreadsPrefix.size());
|
||||
std::size_t count = 0;
|
||||
const auto [end, error] =
|
||||
std::from_chars(value.data(), value.data() + value.size(), count);
|
||||
if (error != std::errc{} || end != value.data() + value.size() ||
|
||||
count < kMinThreadCount || count > kMaxThreadCount)
|
||||
{
|
||||
return std::unexpected("Invalid value for --threads: '" +
|
||||
std::string(value) + "' (expected 1..256)");
|
||||
}
|
||||
result.config.thread_count = count;
|
||||
}
|
||||
else if (argument.starts_with(kInterpreterPrefix))
|
||||
{
|
||||
const auto value = argument.substr(kInterpreterPrefix.size());
|
||||
if (value.empty())
|
||||
return std::unexpected("--interpreter requires a non-empty path");
|
||||
result.config.interpreter_path = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::unexpected("Unknown argument: " + std::string(argument));
|
||||
}
|
||||
}
|
||||
|
||||
return config_;
|
||||
return result;
|
||||
}
|
||||
|
||||
const ServerConfig& ArgsParser::GetConfig() const
|
||||
void PrintHelp(std::ostream& output, std::string_view program_name)
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
|
||||
void ArgsParser::SetupLogger(const ServerConfig& config)
|
||||
{
|
||||
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e [%t] [%^%l%$] %v");
|
||||
|
||||
if (!config.log_file.empty())
|
||||
{
|
||||
auto file_logger = spdlog::basic_logger_mt("file_logger", config.log_file);
|
||||
file_logger->set_level(config.log_level);
|
||||
spdlog::set_default_logger(file_logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto console_logger = config.use_stderr ? spdlog::stderr_logger_mt("console_logger") : spdlog::stdout_logger_mt("console_logger");
|
||||
console_logger->set_level(config.log_level);
|
||||
spdlog::set_default_logger(console_logger);
|
||||
}
|
||||
|
||||
spdlog::set_level(config.log_level);
|
||||
}
|
||||
|
||||
void ArgsParser::PrintHelp(const std::string& program_name)
|
||||
{
|
||||
std::cout << "Usage: " << program_name << " [options]\\n\\n"
|
||||
<< "Options:\\n"
|
||||
<< " --help Show this help message\\n"
|
||||
<< " --log=<level> Set log level (trace, debug, info, warn, error, off)\\n"
|
||||
<< " --log-stderr Output logs to stderr (default)\\n"
|
||||
<< " --log-stdout Output logs to stdout\\n"
|
||||
<< " --log-file=<path> Output logs to specified file\\n"
|
||||
<< " --use-stdio Alias for --log-stderr (keep stdout clean for LSP)\\n"
|
||||
<< " --threads=<count> Number of worker threads\\n"
|
||||
<< " --interpreter=<path> Custom interpreter path\\n";
|
||||
output << "Usage: " << program_name << " [options]\n\n"
|
||||
<< "Options:\n"
|
||||
<< " --help Show this help message\n"
|
||||
<< " --log=<level> Set log level (trace, debug, info, warn, error, off)\n"
|
||||
<< " --log-file=<path> Output logs to specified file\n"
|
||||
<< " --threads=<count> Number of worker threads (1-256)\n"
|
||||
<< " --interpreter=<path> Custom interpreter path\n";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user