Skip to content

Commit 3d15f21

Browse files
author
Watson
committed
Integrate fmt/spdlog hooks and add explicit render status error handling
1 parent c498262 commit 3d15f21

10 files changed

Lines changed: 230 additions & 11 deletions

File tree

CMakeLists.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ option(ENABLE_PLOTTING "Enable gnuplot rendering support" ON)
55
option(BUILD_TESTING "Build tests" ON)
66
option(BUILD_EXAMPLES "Build examples" ON)
77
option(GNUPLOTPP_ENABLE_CPM "Enable CPM-managed external dependencies" OFF)
8+
option(GNUPLOTPP_ENABLE_LOGGING "Enable fmt/spdlog logging integration when available" ON)
89

910
set(CMAKE_CXX_STANDARD 20)
1011
set(CMAKE_CXX_STANDARD_REQUIRED ON)
1112
set(CMAKE_CXX_EXTENSIONS OFF)
1213

1314
include(cmake/dependencies.cmake)
1415

16+
if(GNUPLOTPP_ENABLE_LOGGING AND NOT GNUPLOTPP_ENABLE_CPM)
17+
find_package(fmt CONFIG QUIET)
18+
find_package(spdlog CONFIG QUIET)
19+
endif()
20+
1521
add_library(gnuplotpp
1622
src/figure.cpp
1723
src/gnuplot_backend.cpp
@@ -33,6 +39,24 @@ if(TARGET nlohmann_json::nlohmann_json)
3339
target_link_libraries(gnuplotpp PUBLIC nlohmann_json::nlohmann_json)
3440
endif()
3541

42+
if(GNUPLOTPP_ENABLE_LOGGING)
43+
if(TARGET fmt::fmt)
44+
target_link_libraries(gnuplotpp PUBLIC fmt::fmt)
45+
target_compile_definitions(gnuplotpp PUBLIC GNUPLOTPP_HAS_FMT=1)
46+
elseif(TARGET fmt::fmt-header-only)
47+
target_link_libraries(gnuplotpp PUBLIC fmt::fmt-header-only)
48+
target_compile_definitions(gnuplotpp PUBLIC GNUPLOTPP_HAS_FMT=1)
49+
endif()
50+
51+
if(TARGET spdlog::spdlog)
52+
target_link_libraries(gnuplotpp PUBLIC spdlog::spdlog)
53+
target_compile_definitions(gnuplotpp PUBLIC GNUPLOTPP_HAS_SPDLOG=1)
54+
elseif(TARGET spdlog::spdlog_header_only)
55+
target_link_libraries(gnuplotpp PUBLIC spdlog::spdlog_header_only)
56+
target_compile_definitions(gnuplotpp PUBLIC GNUPLOTPP_HAS_SPDLOG=1)
57+
endif()
58+
endif()
59+
3660
if(BUILD_EXAMPLES)
3761
add_executable(two_window_example examples/two_window_example.cpp)
3862
target_link_libraries(two_window_example PRIVATE gnuplotpp::gnuplotpp)

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,22 @@ sequenceDiagram
5353
## CPM Dependencies (inside CMake)
5454

5555
CPM is optional via `GNUPLOTPP_ENABLE_CPM=ON`.
56-
When enabled, CMake downloads `CPM.cmake` and resolves C++ libraries (currently `nlohmann_json`).
56+
When enabled, CMake downloads `CPM.cmake` and resolves C++ libraries (`nlohmann_json`, `fmt`, `spdlog`).
57+
If network/package resolution fails, configuration now degrades with warnings and continues.
5758

5859
## Gnuplot and CPM
5960

6061
- CPM is best for CMake/C++ dependencies.
6162
- `gnuplot` is an external CLI renderer.
6263
- Recommended: install `gnuplot` with system package managers and keep CPM for C++ libs.
6364

65+
## Logging and Error Handling
66+
67+
- `RenderResult` includes `RenderStatus` (`Success`, `InvalidInput`, `IoError`, `ExternalToolMissing`, `ExternalToolFailure`, `UnsupportedFormat`).
68+
- Backends emit detailed file/tool failure messages and return explicit statuses.
69+
- If `fmt` and `spdlog` are available (via CPM or system packages), the library uses them for structured formatting/logging.
70+
- If not available, behavior falls back to standard C++ streams with the same status semantics.
71+
6472
## No Separate Plot Command
6573

6674
You run only the C++ executable.

cmake/dependencies.cmake

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,43 @@ set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}
77

88
if(NOT EXISTS ${CPM_DOWNLOAD_LOCATION})
99
message(STATUS "Downloading CPM.cmake v${CPM_DOWNLOAD_VERSION}")
10-
file(
11-
DOWNLOAD
10+
file(DOWNLOAD
1211
"https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake"
1312
${CPM_DOWNLOAD_LOCATION}
1413
EXPECTED_HASH SHA256=302f3ee95d731d94def0707260f6f8c9d7079682933c5d5c6d4218f292362766
14+
STATUS cpm_download_status
15+
LOG cpm_download_log
1516
)
17+
list(GET cpm_download_status 0 cpm_download_code)
18+
if(NOT cpm_download_code EQUAL 0)
19+
list(GET cpm_download_status 1 cpm_download_reason)
20+
message(WARNING "CPM download failed (${cpm_download_reason}); continuing without CPM packages.")
21+
return()
22+
endif()
1623
endif()
1724

1825
include(${CPM_DOWNLOAD_LOCATION})
26+
if(NOT COMMAND CPMAddPackage)
27+
message(WARNING "CPMAddPackage is unavailable; continuing without CPM packages.")
28+
return()
29+
endif()
1930

2031
CPMAddPackage(
2132
NAME nlohmann_json
2233
GITHUB_REPOSITORY nlohmann/json
2334
VERSION 3.11.3
2435
OPTIONS "JSON_BuildTests OFF"
2536
)
37+
38+
CPMAddPackage(
39+
NAME fmt
40+
GITHUB_REPOSITORY fmtlib/fmt
41+
VERSION 11.0.2
42+
)
43+
44+
CPMAddPackage(
45+
NAME spdlog
46+
GITHUB_REPOSITORY gabime/spdlog
47+
VERSION 1.14.1
48+
OPTIONS "SPDLOG_FMT_EXTERNAL ON"
49+
)

include/gnuplotpp/plot.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,20 @@ class Axes {
125125

126126
class Figure;
127127

128+
/** @brief Backend render status and generated output paths. */
129+
enum class RenderStatus {
130+
Success,
131+
InvalidInput,
132+
IoError,
133+
ExternalToolMissing,
134+
ExternalToolFailure,
135+
UnsupportedFormat
136+
};
137+
128138
/** @brief Backend render status and generated output paths. */
129139
struct RenderResult {
130140
bool ok = true;
141+
RenderStatus status = RenderStatus::Success;
131142
std::string message;
132143
std::filesystem::path script_path;
133144
std::vector<std::filesystem::path> outputs;

src/figure.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Axes& Figure::axes(int idx) {
4444
RenderResult Figure::save(const std::filesystem::path& out_dir) const {
4545
if (!backend_) {
4646
return RenderResult{.ok = false,
47+
.status = RenderStatus::InvalidInput,
4748
.message =
4849
"No backend configured. Call set_backend() before save()."};
4950
}

src/gnuplot_backend.cpp

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,62 @@
44
#include <filesystem>
55
#include <fstream>
66
#include <iomanip>
7+
#include <iostream>
78
#include <sstream>
89
#include <string>
910
#include <utility>
1011

12+
#ifdef GNUPLOTPP_HAS_FMT
13+
#include <fmt/format.h>
14+
#endif
15+
16+
#ifdef GNUPLOTPP_HAS_SPDLOG
17+
#include <spdlog/spdlog.h>
18+
#endif
19+
1120
namespace gnuplotpp {
1221
namespace {
1322

1423
std::string quote(const std::filesystem::path& p) {
1524
return "'" + p.string() + "'";
1625
}
1726

27+
std::string msg_io(const std::string& prefix,
28+
const std::filesystem::path& path,
29+
const std::error_code& ec) {
30+
#ifdef GNUPLOTPP_HAS_FMT
31+
return fmt::format("{} '{}': {}", prefix, path.string(), ec.message());
32+
#else
33+
std::ostringstream os;
34+
os << prefix << " '" << path.string() << "': " << ec.message();
35+
return os.str();
36+
#endif
37+
}
38+
39+
std::string msg_text(const std::string& prefix, const std::string& detail) {
40+
#ifdef GNUPLOTPP_HAS_FMT
41+
return fmt::format("{}: {}", prefix, detail);
42+
#else
43+
return prefix + ": " + detail;
44+
#endif
45+
}
46+
47+
void log_info(const std::string& msg) {
48+
#ifdef GNUPLOTPP_HAS_SPDLOG
49+
spdlog::info("{}", msg);
50+
#else
51+
std::clog << "[gnuplotpp] info: " << msg << "\n";
52+
#endif
53+
}
54+
55+
void log_error(const std::string& msg) {
56+
#ifdef GNUPLOTPP_HAS_SPDLOG
57+
spdlog::error("{}", msg);
58+
#else
59+
std::cerr << "[gnuplotpp] error: " << msg << "\n";
60+
#endif
61+
}
62+
1863
std::string extension_for(OutputFormat format) {
1964
switch (format) {
2065
case OutputFormat::Pdf:
@@ -166,12 +211,15 @@ GnuplotBackend::GnuplotBackend(std::string executable)
166211
RenderResult GnuplotBackend::render(const Figure& fig,
167212
const std::filesystem::path& out_dir) {
168213
RenderResult result;
214+
result.status = RenderStatus::Success;
169215

170216
std::error_code ec;
171217
std::filesystem::create_directories(out_dir / "tmp", ec);
172218
if (ec) {
173219
result.ok = false;
174-
result.message = "failed to create output directories: " + ec.message();
220+
result.status = RenderStatus::IoError;
221+
result.message = msg_io("failed to create output directories", out_dir / "tmp", ec);
222+
log_error(result.message);
175223
return result;
176224
}
177225

@@ -190,17 +238,38 @@ RenderResult GnuplotBackend::render(const Figure& fig,
190238

191239
const auto& series = axis.series()[series_idx];
192240
std::ofstream data_os(data_path);
241+
if (!data_os.is_open()) {
242+
result.ok = false;
243+
result.status = RenderStatus::IoError;
244+
result.message = msg_text("failed to open data file for writing", data_path.string());
245+
log_error(result.message);
246+
return result;
247+
}
193248
data_os << std::scientific << std::setprecision(16);
194249
for (std::size_t i = 0; i < series.x.size(); ++i) {
195250
data_os << series.x[i] << " " << series.y[i] << "\n";
196251
}
252+
if (!data_os.good()) {
253+
result.ok = false;
254+
result.status = RenderStatus::IoError;
255+
result.message = msg_text("failed while writing data file", data_path.string());
256+
log_error(result.message);
257+
return result;
258+
}
197259
}
198260
}
199261

200262
const auto script_path = out_dir / "tmp" / "figure.gp";
201263
result.script_path = script_path;
202264

203265
std::ofstream script_os(script_path);
266+
if (!script_os.is_open()) {
267+
result.ok = false;
268+
result.status = RenderStatus::IoError;
269+
result.message = msg_text("failed to open gnuplot script for writing", script_path.string());
270+
log_error(result.message);
271+
return result;
272+
}
204273
script_os << "set encoding utf8\n";
205274

206275
for (const auto format : fig.spec().formats) {
@@ -212,23 +281,44 @@ RenderResult GnuplotBackend::render(const Figure& fig,
212281
emit_plot_body(script_os, fig, data_files);
213282
script_os << "set output\n";
214283
}
284+
if (!script_os.good()) {
285+
result.ok = false;
286+
result.status = RenderStatus::IoError;
287+
result.message = msg_text("failed while writing gnuplot script", script_path.string());
288+
log_error(result.message);
289+
return result;
290+
}
215291

216292
const std::string check_cmd = "command -v " + executable_ + " >/dev/null 2>&1";
217293
if (std::system(check_cmd.c_str()) != 0) {
218294
result.ok = false;
219-
result.message = "gnuplot executable not found; generated script/data only";
295+
result.status = RenderStatus::ExternalToolMissing;
296+
result.message = msg_text("gnuplot executable not found; generated script/data only",
297+
executable_);
298+
log_error(result.message);
220299
return result;
221300
}
222301

223-
const std::string render_cmd = executable_ + " " + script_path.string() + " >/tmp/gnuplotpp.log 2>&1";
224-
if (std::system(render_cmd.c_str()) != 0) {
302+
const std::string render_cmd =
303+
executable_ + " " + quote(script_path) + " >/tmp/gnuplotpp.log 2>&1";
304+
const int rc = std::system(render_cmd.c_str());
305+
if (rc != 0) {
225306
result.ok = false;
307+
result.status = RenderStatus::ExternalToolFailure;
308+
#ifdef GNUPLOTPP_HAS_FMT
309+
result.message =
310+
fmt::format("gnuplot failed (exit={}); inspect /tmp/gnuplotpp.log", rc);
311+
#else
226312
result.message = "gnuplot failed; inspect /tmp/gnuplotpp.log";
313+
#endif
314+
log_error(result.message);
227315
return result;
228316
}
229317

230318
result.ok = true;
319+
result.status = RenderStatus::Success;
231320
result.message = "render completed";
321+
log_info(result.message);
232322
return result;
233323
}
234324

0 commit comments

Comments
 (0)