Extract attach-bsdl into core; add it to the wx GUI.
Second editing op into the wx frontend. Extract the logic (parse the .bsd,
apply it to the part, record bsdl_path) into core/app/edit.hpp::attach_bsdl
(Part*, path) -> {ok, error, entity, bound, unbound, ports_total}, failing
without mutation when the file can't be parsed.
The TUI `attach-bsdl` command now renders that result (output unchanged); the
wx GUI gains an Edit ▸ Attach BSDL… menu reusing PickPart() + a .bsd file
dialog. Prune the now-dead bsdl_model include from commands.cpp.
tests/test_edit.cpp: parse failure leaves the part untouched; null part. The
success path is covered by a batch run (entity + bound/total ports). 381 core
assertions green; tui + wx build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "core/app/edit.hpp"
|
||||
|
||||
#include "core/domain/bsdl_model.hpp"
|
||||
#include "core/domain/parts.hpp"
|
||||
#include "core/domain/pin_model.hpp"
|
||||
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
||||
@@ -28,4 +29,29 @@ SetConnectorTypeResult set_connector_type(Part *part, const std::string &kind)
|
||||
return r;
|
||||
}
|
||||
|
||||
AttachBsdlResult attach_bsdl(Part *part, const std::string &path)
|
||||
{
|
||||
AttachBsdlResult r;
|
||||
if (!part) {
|
||||
r.error = "no part";
|
||||
return r;
|
||||
}
|
||||
|
||||
BsdlModel model = BsdlModel::from_file(path);
|
||||
if (!model.valid()) {
|
||||
r.error = "cannot parse " + path
|
||||
+ (model.error().empty() ? "" : (": " + model.error()));
|
||||
return r;
|
||||
}
|
||||
|
||||
BsdlApplyReport rep = apply_bsdl(part, model);
|
||||
part->bsdl_path = path;
|
||||
r.entity = model.entity();
|
||||
r.bound = rep.bound;
|
||||
r.unbound = rep.unbound;
|
||||
r.ports_total = (int)model.ports().size();
|
||||
r.ok = true;
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
@@ -22,6 +22,22 @@ struct SetConnectorTypeResult {
|
||||
// (ok=false, error set, no mutation) when the kind is invalid for the part.
|
||||
SetConnectorTypeResult set_connector_type(Part *part, const std::string &kind);
|
||||
|
||||
// Outcome of attaching a BSDL model to a part. On success the part's pin specs
|
||||
// are filled from the model and its bsdl_path is recorded.
|
||||
struct AttachBsdlResult {
|
||||
bool ok = false;
|
||||
std::string error; ///< set when the .bsd cannot be parsed
|
||||
std::string entity; ///< the BSDL entity name
|
||||
int bound = 0; ///< ports matched to a pin
|
||||
int unbound = 0; ///< ports with no matching pin
|
||||
int ports_total = 0; ///< ports declared in the model
|
||||
};
|
||||
|
||||
// Parse the BSDL file at `path` and apply it to `part` (fills each pin's role
|
||||
// and direction; records bsdl_path). Fails (ok=false, error set, no mutation)
|
||||
// when the file cannot be parsed.
|
||||
AttachBsdlResult attach_bsdl(Part *part, const std::string &path);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif // _APP_EDIT_HPP_
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "core/domain/parts.hpp"
|
||||
#include "core/domain/persist.hpp"
|
||||
#include "core/domain/pin_role.hpp"
|
||||
#include "core/domain/bsdl_model.hpp"
|
||||
#include "core/domain/pins.hpp"
|
||||
#include "core/domain/signals.hpp"
|
||||
#include "core/domain/system.hpp"
|
||||
@@ -445,17 +444,11 @@ void Tui::RegisterCommands() {
|
||||
}
|
||||
}
|
||||
|
||||
BsdlModel model = BsdlModel::from_file(args[2]);
|
||||
if (!model.valid()) {
|
||||
Print("attach-bsdl: cannot parse " + args[2]
|
||||
+ (model.error().empty() ? "" : (": " + model.error())));
|
||||
return;
|
||||
}
|
||||
BsdlApplyReport r = apply_bsdl(prt, model);
|
||||
prt->bsdl_path = args[2];
|
||||
Print(mod->name + "/" + prt->name + ": attached BSDL '" + model.entity()
|
||||
app::AttachBsdlResult r = app::attach_bsdl(prt, args[2]);
|
||||
if (!r.ok) { Print("attach-bsdl: " + r.error); return; }
|
||||
Print(mod->name + "/" + prt->name + ": attached BSDL '" + r.entity
|
||||
+ "' — " + std::to_string(r.bound) + "/"
|
||||
+ std::to_string((int)model.ports().size()) + " ports bound"
|
||||
+ std::to_string(r.ports_total) + " ports bound"
|
||||
+ (r.unbound ? (", " + std::to_string(r.unbound) + " unbound") : ""));
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
|
||||
@@ -33,6 +33,7 @@ enum {
|
||||
ID_SAVE,
|
||||
ID_EXPORT,
|
||||
ID_SET_CONNECTOR_TYPE,
|
||||
ID_ATTACH_BSDL,
|
||||
ID_VERIFY,
|
||||
ID_QUIT,
|
||||
ID_ABOUT,
|
||||
@@ -57,6 +58,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
||||
|
||||
auto *edit = new wxMenu;
|
||||
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
|
||||
edit->Append(ID_ATTACH_BSDL, "Attach &BSDL…\tCtrl-B");
|
||||
|
||||
auto *sysm = new wxMenu;
|
||||
sysm->Append(ID_VERIFY, "&Verify\tCtrl-K");
|
||||
@@ -100,6 +102,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnSave, this, ID_SAVE);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnExport, this, ID_EXPORT);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnSetConnectorType, this, ID_SET_CONNECTOR_TYPE);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnAttachBsdl, this, ID_ATTACH_BSDL);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnVerify, this, ID_VERIFY);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT);
|
||||
@@ -309,6 +312,30 @@ void EssimFrame::OnSetConnectorType(wxCommandEvent &) {
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
void EssimFrame::OnAttachBsdl(wxCommandEvent &) {
|
||||
Part *p = PickPart();
|
||||
if (!p) return;
|
||||
|
||||
wxFileDialog dlg(this, "Attach a BSDL model", "", "",
|
||||
"BSDL files (*.bsd)|*.bsd|All files (*.*)|*.*",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
|
||||
app::AttachBsdlResult r = app::attach_bsdl(p, dlg.GetPath().utf8_string());
|
||||
if (!r.ok) {
|
||||
Log("attach-bsdl: " + wx(r.error));
|
||||
wxMessageBox(wx(r.error), "Attach BSDL failed", wxOK | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
wxString who = (p->prnt ? wx(p->prnt->name) + "/" : wxString()) + wx(p->name);
|
||||
wxString tail = r.unbound ? wxString::Format(", %d unbound", r.unbound)
|
||||
: wxString();
|
||||
Log(wxString::Format("%s: attached BSDL '%s' — %d/%d ports bound%s",
|
||||
who, wx(r.entity), r.bound, r.ports_total, tail));
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
void EssimFrame::OnVerify(wxCommandEvent &) {
|
||||
app::VerifyReport r = app::verify(fe_.system());
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ private:
|
||||
void OnSave(wxCommandEvent &);
|
||||
void OnExport(wxCommandEvent &);
|
||||
void OnSetConnectorType(wxCommandEvent &);
|
||||
void OnAttachBsdl(wxCommandEvent &);
|
||||
void OnVerify(wxCommandEvent &);
|
||||
void OnQuit(wxCommandEvent &);
|
||||
void OnAbout(wxCommandEvent &);
|
||||
|
||||
@@ -37,3 +37,20 @@ TEST_CASE("set_connector_type on a null part fails cleanly") {
|
||||
CHECK_FALSE(r.ok);
|
||||
CHECK_FALSE(r.error.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("attach_bsdl reports a parse failure without mutating the part") {
|
||||
Part p("J1");
|
||||
p.add(new Pin("1"));
|
||||
|
||||
app::AttachBsdlResult r = app::attach_bsdl(&p, "/nonexistent-xyz/none.bsd");
|
||||
|
||||
CHECK_FALSE(r.ok);
|
||||
CHECK(r.error.find("cannot parse") != std::string::npos);
|
||||
CHECK(p.bsdl_path.empty()); // failure leaves the part untouched
|
||||
}
|
||||
|
||||
TEST_CASE("attach_bsdl on a null part fails cleanly") {
|
||||
app::AttachBsdlResult r = app::attach_bsdl(nullptr, "x.bsd");
|
||||
CHECK_FALSE(r.ok);
|
||||
CHECK_FALSE(r.error.empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user