feat(nonlinear-static-newton-raphson): step 0 - cli-output-lease-and-derived-names

This commit is contained in:
KOKO\Mimi
2026-08-24 10:42:14 +09:00
parent 41816c8aef
commit fc16ef6221
61 changed files with 10890 additions and 115 deletions
+14 -26
View File
@@ -8,6 +8,7 @@
#include "fesa/analysis/analysis.h"
#include "fesa/analysis/linear_static_analysis.h"
#include "fesa/app/output_paths.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/core/diagnostic.h"
#include "fesa/io/hdf5/hdf5_results_writer.h"
@@ -23,19 +24,6 @@ constexpr int kModelExitCode = 4;
constexpr int kSolverExitCode = 5;
constexpr int kOutputExitCode = 6;
bool StartsWithOption(const std::string& argument) {
return !argument.empty() && argument.front() == '-';
}
Diagnostic UsageDiagnostic() {
return {Severity::kError,
"cli-usage",
{{}, 0U},
"",
"",
"Usage: fesa.exe <model.inp> [--output <results.h5>]."};
}
const char* SeverityName(const Severity severity) {
return severity == Severity::kWarning ? "warning" : "error";
}
@@ -72,23 +60,23 @@ int ExitCodeFor(const Status& status) {
} // namespace
int FesaApplication::Run(const std::vector<std::string>& arguments) {
const bool default_output_form = arguments.size() == 1U &&
!arguments[0U].empty() &&
!StartsWithOption(arguments[0U]);
const bool explicit_output_form =
arguments.size() == 3U && !arguments[0U].empty() &&
!StartsWithOption(arguments[0U]) && arguments[1U] == "--output" &&
!arguments[2U].empty() && !StartsWithOption(arguments[2U]);
if (!default_output_form && !explicit_output_form) {
WriteDiagnostics({UsageDiagnostic()});
const auto paths = DeriveSingleInputOutputPaths(
arguments, std::filesystem::current_path());
if (!paths.HasValue()) {
WriteDiagnostics(paths.GetStatus().Diagnostics());
return kUsageExitCode;
}
auto lease = AcquireOutputWriteLease(paths.Value());
if (!lease.HasValue()) {
WriteDiagnostics(lease.GetStatus().Diagnostics());
return kOutputExitCode;
}
AnalysisRequest request;
request.input_path = arguments[0U];
request.output_path = explicit_output_form
? std::filesystem::path{arguments[2U]}
: std::filesystem::current_path() / "results.h5";
request.input_path = paths.Value().input_path;
request.output_path = paths.Value().results_path;
request.log_path = paths.Value().log_path;
TbbParallelFor parallel_for;
MklPardisoSolver linear_solver;
+127
View File
@@ -0,0 +1,127 @@
#include "fesa/app/output_paths.h"
#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include <windows.h>
#include "fesa/core/ascii.h"
namespace fesa {
namespace {
Status UsageFailure(const std::string& identity, const std::string& message) {
return Status::Failure(
FailureCategory::kInput,
{{Severity::kError, "cli-usage", {{}, 0U}, "", identity, message}});
}
Status OutputBusyFailure(const std::filesystem::path& results_path) {
return Status::Failure(
FailureCategory::kOutput,
{{Severity::kError,
"output-busy",
{{}, 0U},
"",
results_path.generic_u8string(),
"The derived output pair is already owned by another invocation."}});
}
bool StartsWithOption(const std::string& argument) {
return !argument.empty() && argument.front() == '-';
}
std::string ArgumentIdentity(const std::size_t argument_index,
const std::string& argument) {
return "argument_index=" + std::to_string(argument_index) +
";argument=" + argument;
}
std::filesystem::path LockPathFor(const DerivedOutputPaths& paths) {
std::filesystem::path lock_path = paths.results_path;
lock_path += ".fesa.lock";
return lock_path;
}
} // namespace
class OutputWriteLease::Impl {
public:
explicit Impl(const HANDLE handle) noexcept : handle_{handle} {}
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
~Impl() {
if (handle_ != INVALID_HANDLE_VALUE) {
(void)CloseHandle(handle_);
}
}
private:
HANDLE handle_{INVALID_HANDLE_VALUE};
};
OutputWriteLease::OutputWriteLease(std::unique_ptr<Impl> impl) noexcept
: impl_{std::move(impl)} {}
OutputWriteLease::OutputWriteLease(OutputWriteLease&&) noexcept = default;
OutputWriteLease& OutputWriteLease::operator=(OutputWriteLease&&) noexcept =
default;
OutputWriteLease::~OutputWriteLease() = default;
Result<DerivedOutputPaths> DeriveSingleInputOutputPaths(
const std::vector<std::string>& arguments,
const std::filesystem::path& invocation_cwd) {
for (std::size_t index = 0U; index < arguments.size(); ++index) {
if (arguments[index].find("--output") != std::string::npos) {
return Result<DerivedOutputPaths>::Failure(UsageFailure(
ArgumentIdentity(index, arguments[index]),
"The --output option is not supported."));
}
}
if (arguments.size() != 1U || arguments.front().empty() ||
StartsWithOption(arguments.front())) {
return Result<DerivedOutputPaths>::Failure(
UsageFailure("", "Usage: fesa.exe <model.inp>."));
}
std::filesystem::path input_path{arguments.front()};
if (input_path.is_relative()) {
input_path = invocation_cwd / input_path;
}
input_path = input_path.lexically_normal();
if (!AsciiCaseInsensitiveEquals(input_path.extension().string(), ".inp")) {
return Result<DerivedOutputPaths>::Failure(
UsageFailure(input_path.generic_u8string(),
"The input path must end in .inp."));
}
const std::filesystem::path stem = input_path.stem();
const std::filesystem::path output_directory = input_path.parent_path();
return Result<DerivedOutputPaths>::Success(
{input_path, output_directory / (stem.string() + "_results.h5"),
output_directory / (stem.string() + "_log.txt")});
}
Result<OutputWriteLease> AcquireOutputWriteLease(
const DerivedOutputPaths& paths) {
const std::filesystem::path lock_path = LockPathFor(paths);
const HANDLE lock_handle = CreateFileW(
lock_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0U, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
if (lock_handle == INVALID_HANDLE_VALUE) {
return Result<OutputWriteLease>::Failure(
OutputBusyFailure(paths.results_path));
}
return Result<OutputWriteLease>::Success(
OutputWriteLease{std::make_unique<OutputWriteLease::Impl>(lock_handle)});
}
} // namespace fesa