Skip to content

Commit a7495af

Browse files
committed
feat(ogar-from-ruff): add Python/Odoo lift (lift_model_python, lift_model_graph_python)
The ruff_python_spo frontend (ruff #34, merged to main at 80d7f2cb) emits the Odoo/Python SPO corpus under the `odoo` namespace. OGAR's lift previously hard-coded `Language::Ruby`, so an Odoo ModelGraph lifted with the wrong producer-language discriminant. Refactor `lift_model` / `lift_model_graph` to delegate to private `lift_model_with_language` / `lift_model_graph_with_language` and add public `lift_model_python` / `lift_model_graph_python` that stamp `Language::Python`. The projection is otherwise identical: the `odoo` namespace already routes to the `erp` source domain and `odoo` curator via the existing `classify_domain` path, so the Python wrapper changes only the language discriminant, nothing else. Language is set explicitly (per producer) rather than guessed from `ModelGraph::namespace`, because the namespace (openproject, odoo, …) does not bind one-to-one to the producer language. Two regression tests: - lift_model_python_stamps_python_language - lift_model_graph_python_stamps_python_and_keeps_erp_domain Verified via an isolated probe workspace (path-dep real ogar-vocab + git-dep ruff main @ 80d7f2cb): 28 tests pass, clippy -D warnings clean. The OGAR workspace itself cannot resolve offline (surrealdb git dep 403 via ogar-adapter-surrealql), so the probe is the verification path. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a1fb170 commit a7495af

1 file changed

Lines changed: 59 additions & 10 deletions

File tree

  • crates/ogar-from-ruff/src

crates/ogar-from-ruff/src/lib.rs

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,25 @@ use ruff_spo_triplet::{
7373
ModelGraph, ScopeDecl, ScopeKind, StiInfo, Validation as RuffValidation, ValidationKind,
7474
};
7575

76-
/// Lift every model in a [`ModelGraph`] to an OGAR [`Class`]. Output
77-
/// preserves declaration order so downstream consumers can rely on
78-
/// deterministic ordering for snapshot tests.
76+
/// Lift every model in a [`ModelGraph`] to an OGAR [`Class`] (Rails /
77+
/// `ruff_ruby_spo` producer — [`Language::Ruby`]). Output preserves
78+
/// declaration order so downstream consumers can rely on deterministic
79+
/// ordering for snapshot tests.
7980
#[must_use]
8081
pub fn lift_model_graph(graph: &ModelGraph) -> Vec<Class> {
82+
lift_model_graph_with_language(graph, Language::Ruby)
83+
}
84+
85+
/// Lift a whole [`ModelGraph`] for a Python / Odoo producer
86+
/// (`ruff_python_spo`) — like [`lift_model_graph`] but stamps each class as
87+
/// [`Language::Python`]. The `odoo` namespace already routes to the `erp`
88+
/// source domain via the same `classify_domain` path.
89+
#[must_use]
90+
pub fn lift_model_graph_python(graph: &ModelGraph) -> Vec<Class> {
91+
lift_model_graph_with_language(graph, Language::Python)
92+
}
93+
94+
fn lift_model_graph_with_language(graph: &ModelGraph, language: Language) -> Vec<Class> {
8195
let domain = classify_domain(&graph.namespace);
8296
let concept_domain = domain.as_deref().and_then(ogar_vocab::source_domain_concept);
8397
// The harvest namespace IS the curator id (`"openproject"`,
@@ -94,7 +108,7 @@ pub fn lift_model_graph(graph: &ModelGraph) -> Vec<Class> {
94108
.models
95109
.iter()
96110
.map(|m| {
97-
let mut class = lift_model(m);
111+
let mut class = lift_model_with_language(m, language);
98112
class.source_domain = domain.clone();
99113
class.source_curator = curator.clone();
100114
// Domain-gate the canonical concept. `lift_model` resolves
@@ -130,18 +144,31 @@ fn classify_domain(namespace: &str) -> Option<String> {
130144
}
131145
}
132146

133-
/// Lift one [`Model`] to an OGAR [`Class`]. Pure projection — no I/O.
147+
/// Lift one [`Model`] to an OGAR [`Class`] stamped as [`Language::Ruby`]
148+
/// (the Rails / `ruff_ruby_spo` producer). Pure projection — no I/O.
134149
///
135-
/// **Language is always [`Language::Ruby`]** at this layer. Other
136-
/// frontends (Python/Elixir/etc.) using `ruff_spo_triplet::Model` will
137-
/// likely want their own wrapper that sets the discriminant. We don't
138-
/// guess it from `ModelGraph::namespace` because the namespace
150+
/// For the Python / Odoo producer (`ruff_python_spo`) use
151+
/// [`lift_model_python`]. Both delegate to the same projection and differ
152+
/// only in the language discriminant. Language is set explicitly rather
153+
/// than guessed from `ModelGraph::namespace`, because the namespace
139154
/// (`openproject`, `odoo`, …) doesn't bind to the producer language
140155
/// one-to-one.
141156
#[must_use]
142157
pub fn lift_model(model: &Model) -> Class {
158+
lift_model_with_language(model, Language::Ruby)
159+
}
160+
161+
/// Lift one [`Model`] to an OGAR [`Class`] stamped as [`Language::Python`]
162+
/// — the Odoo / Django producer path (`ruff_python_spo`). Identical
163+
/// projection to [`lift_model`]; only the language discriminant differs.
164+
#[must_use]
165+
pub fn lift_model_python(model: &Model) -> Class {
166+
lift_model_with_language(model, Language::Python)
167+
}
168+
169+
fn lift_model_with_language(model: &Model, language: Language) -> Class {
143170
let mut class = Class::new(&model.name);
144-
class.language = Language::Ruby;
171+
class.language = language;
145172
class.parent = model.sti.as_ref().and_then(sti_parent);
146173
class.inheritance = lift_inheritance(model);
147174
class.canonical_concept = Some(canonical_concept(&model.name));
@@ -667,6 +694,28 @@ mod tests {
667694
assert!(matches!(class.language, Language::Ruby));
668695
}
669696

697+
#[test]
698+
fn lift_model_python_stamps_python_language() {
699+
// The Python/Odoo producer path: same projection, Python discriminant.
700+
let class = lift_model_python(&mk_model());
701+
assert!(matches!(class.language, Language::Python));
702+
assert_eq!(class.name, "WorkPackage");
703+
assert_eq!(class.parent.as_deref(), Some("Issue"));
704+
}
705+
706+
#[test]
707+
fn lift_model_graph_python_stamps_python_and_keeps_erp_domain() {
708+
// An Odoo ModelGraph (namespace "odoo") lifts as Python and routes to
709+
// the `erp` source domain / `odoo` curator via classify_domain.
710+
let mut graph = ModelGraph::new("odoo");
711+
graph.models.push(Model::new("account_move"));
712+
let classes = lift_model_graph_python(&graph);
713+
assert_eq!(classes.len(), 1);
714+
assert!(matches!(classes[0].language, Language::Python));
715+
assert_eq!(classes[0].source_domain.as_deref(), Some("erp"));
716+
assert_eq!(classes[0].source_curator.as_deref(), Some("odoo"));
717+
}
718+
670719
#[test]
671720
fn lift_inheritance_concrete_from_sti_parent() {
672721
// mk_model's StiInfo has inherits_from = Some("Issue").

0 commit comments

Comments
 (0)