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/app/edit.hpp"
|
||||||
|
|
||||||
|
#include "core/domain/bsdl_model.hpp"
|
||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/pin_model.hpp"
|
#include "core/domain/pin_model.hpp"
|
||||||
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
||||||
@@ -28,4 +29,29 @@ SetConnectorTypeResult set_connector_type(Part *part, const std::string &kind)
|
|||||||
return r;
|
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
|
} // namespace app
|
||||||
|
|||||||
@@ -22,6 +22,22 @@ struct SetConnectorTypeResult {
|
|||||||
// (ok=false, error set, no mutation) when the kind is invalid for the part.
|
// (ok=false, error set, no mutation) when the kind is invalid for the part.
|
||||||
SetConnectorTypeResult set_connector_type(Part *part, const std::string &kind);
|
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
|
} // namespace app
|
||||||
|
|
||||||
#endif // _APP_EDIT_HPP_
|
#endif // _APP_EDIT_HPP_
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/persist.hpp"
|
#include "core/domain/persist.hpp"
|
||||||
#include "core/domain/pin_role.hpp"
|
#include "core/domain/pin_role.hpp"
|
||||||
#include "core/domain/bsdl_model.hpp"
|
|
||||||
#include "core/domain/pins.hpp"
|
#include "core/domain/pins.hpp"
|
||||||
#include "core/domain/signals.hpp"
|
#include "core/domain/signals.hpp"
|
||||||
#include "core/domain/system.hpp"
|
#include "core/domain/system.hpp"
|
||||||
@@ -445,17 +444,11 @@ void Tui::RegisterCommands() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BsdlModel model = BsdlModel::from_file(args[2]);
|
app::AttachBsdlResult r = app::attach_bsdl(prt, args[2]);
|
||||||
if (!model.valid()) {
|
if (!r.ok) { Print("attach-bsdl: " + r.error); return; }
|
||||||
Print("attach-bsdl: cannot parse " + args[2]
|
Print(mod->name + "/" + prt->name + ": attached BSDL '" + r.entity
|
||||||
+ (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()
|
|
||||||
+ "' — " + std::to_string(r.bound) + "/"
|
+ "' — " + 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") : ""));
|
+ (r.unbound ? (", " + std::to_string(r.unbound) + " unbound") : ""));
|
||||||
},
|
},
|
||||||
/*prompt_for_missing=*/ false,
|
/*prompt_for_missing=*/ false,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ enum {
|
|||||||
ID_SAVE,
|
ID_SAVE,
|
||||||
ID_EXPORT,
|
ID_EXPORT,
|
||||||
ID_SET_CONNECTOR_TYPE,
|
ID_SET_CONNECTOR_TYPE,
|
||||||
|
ID_ATTACH_BSDL,
|
||||||
ID_VERIFY,
|
ID_VERIFY,
|
||||||
ID_QUIT,
|
ID_QUIT,
|
||||||
ID_ABOUT,
|
ID_ABOUT,
|
||||||
@@ -57,6 +58,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
|||||||
|
|
||||||
auto *edit = new wxMenu;
|
auto *edit = new wxMenu;
|
||||||
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
|
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
|
||||||
|
edit->Append(ID_ATTACH_BSDL, "Attach &BSDL…\tCtrl-B");
|
||||||
|
|
||||||
auto *sysm = new wxMenu;
|
auto *sysm = new wxMenu;
|
||||||
sysm->Append(ID_VERIFY, "&Verify\tCtrl-K");
|
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::OnSave, this, ID_SAVE);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnExport, this, ID_EXPORT);
|
Bind(wxEVT_MENU, &EssimFrame::OnExport, this, ID_EXPORT);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnSetConnectorType, this, ID_SET_CONNECTOR_TYPE);
|
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::OnVerify, this, ID_VERIFY);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT);
|
Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT);
|
Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT);
|
||||||
@@ -309,6 +312,30 @@ void EssimFrame::OnSetConnectorType(wxCommandEvent &) {
|
|||||||
RebuildModelView();
|
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 &) {
|
void EssimFrame::OnVerify(wxCommandEvent &) {
|
||||||
app::VerifyReport r = app::verify(fe_.system());
|
app::VerifyReport r = app::verify(fe_.system());
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ private:
|
|||||||
void OnSave(wxCommandEvent &);
|
void OnSave(wxCommandEvent &);
|
||||||
void OnExport(wxCommandEvent &);
|
void OnExport(wxCommandEvent &);
|
||||||
void OnSetConnectorType(wxCommandEvent &);
|
void OnSetConnectorType(wxCommandEvent &);
|
||||||
|
void OnAttachBsdl(wxCommandEvent &);
|
||||||
void OnVerify(wxCommandEvent &);
|
void OnVerify(wxCommandEvent &);
|
||||||
void OnQuit(wxCommandEvent &);
|
void OnQuit(wxCommandEvent &);
|
||||||
void OnAbout(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.ok);
|
||||||
CHECK_FALSE(r.error.empty());
|
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