pub mod workspace_generator;
use portalis_core::{Agent, Error, Phase, PipelineState, Result};
pub use workspace_generator::{WorkspaceGenerator, WorkspaceConfig, CrateInfo, ExternalDependency};
use portalis_ingest::{IngestAgent, IngestInput};
use portalis_analysis::{AnalysisAgent, AnalysisInput};
use portalis_transpiler::{TranspilerAgent, TranspilerInput};
use portalis_build::{BuildAgent, BuildInput};
use portalis_test::{TestAgent, TestInput};
use portalis_packaging::{PackagingAgent, PackagingInput};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub struct Pipeline {
ingest: IngestAgent,
analysis: AnalysisAgent,
transpiler: TranspilerAgent,
build: BuildAgent,
test: TestAgent,
packaging: PackagingAgent,
state: PipelineState,
}
impl Pipeline {
pub fn new() -> Self {
Self {
ingest: IngestAgent::new(),
analysis: AnalysisAgent::new(),
transpiler: TranspilerAgent::new(),
build: BuildAgent::new(),
test: TestAgent::new(),
packaging: PackagingAgent::new(),
state: PipelineState::new(),
}
}
pub async fn translate(&mut self, source_path: PathBuf, source_code: String) -> Result<PipelineOutput> {
tracing::info!("Starting translation pipeline for {:?}", source_path);
self.state.transition(Phase::Ingesting);
let ingest_output = self.ingest.execute(IngestInput {
source_path: source_path.clone(),
source_code,
}).await?;
self.state.transition(Phase::Analyzing);
let ast_json = serde_json::to_value(&ingest_output.ast)
.map_err(|e| Error::Pipeline(format!("Failed to serialize AST: {}", e)))?;
let analysis_output = self.analysis.execute(AnalysisInput {
ast: ast_json,
}).await?;
self.state.transition(Phase::Transpiling);
let typed_functions = serde_json::to_value(&analysis_output.typed_functions)
.map_err(|e| Error::Pipeline(format!("Failed to serialize functions: {}", e)))?;
let api_contract = serde_json::to_value(&analysis_output.api_contract)
.map_err(|e| Error::Pipeline(format!("Failed to serialize API: {}", e)))?;
let transpiler_output = self.transpiler.execute(TranspilerInput {
typed_functions: typed_functions.as_array().cloned().unwrap_or_default(),
typed_classes: vec![],
use_statements: vec![],
cargo_dependencies: vec![],
api_contract,
}).await?;
self.state.transition(Phase::Building);
let build_output = self.build.execute(BuildInput {
rust_code: transpiler_output.rust_code.clone(),
}).await?;
self.state.transition(Phase::Testing);
let test_output = self.test.execute(TestInput {
wasm_bytes: build_output.wasm_bytes.clone(),
}).await?;
self.state.transition(Phase::Packaging);
let test_results = serde_json::to_value(&test_output)
.map_err(|e| Error::Pipeline(format!("Failed to serialize tests: {}", e)))?;
let packaging_output = self.packaging.execute(PackagingInput {
wasm_bytes: build_output.wasm_bytes.clone(),
test_results,
}).await?;
self.state.transition(Phase::Complete);
Ok(PipelineOutput {
source_path,
rust_code: transpiler_output.rust_code,
wasm_bytes: packaging_output.package_bytes,
manifest: packaging_output.manifest,
test_passed: test_output.passed,
test_failed: test_output.failed,
})
}
pub fn state(&self) -> &PipelineState {
&self.state
}
}
impl Default for Pipeline {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineOutput {
pub source_path: PathBuf,
pub rust_code: String,
pub wasm_bytes: Vec<u8>,
pub manifest: serde_json::Value,
pub test_passed: usize,
pub test_failed: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_pipeline_simple_function() {
let mut pipeline = Pipeline::new();
let source = r#"
def add(a: int, b: int) -> int:
return a + b
"#;
let result = pipeline.translate(
PathBuf::from("test.py"),
source.to_string(),
).await;
match result {
Ok(output) => {
assert!(!output.rust_code.is_empty());
println!("Generated Rust:\n{}", output.rust_code);
}
Err(e) => {
println!("Pipeline error (expected if wasm32 target not installed): {}", e);
}
}
}
#[tokio::test]
async fn test_pipeline_fibonacci() {
let mut pipeline = Pipeline::new();
let source = r#"
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
"#;
let result = pipeline.translate(
PathBuf::from("fibonacci.py"),
source.to_string(),
).await;
match result {
Ok(output) => {
assert!(!output.rust_code.is_empty());
assert!(output.rust_code.contains("fibonacci"));
}
Err(e) => {
println!("Pipeline error (expected if wasm32 target not installed): {}", e);
}
}
}
#[tokio::test]
async fn test_pipeline_multiple_functions() {
let mut pipeline = Pipeline::new();
let source = r#"
def add(a: int, b: int) -> int:
return a + b
def multiply(x: int, y: int) -> int:
return x * y
"#;
let result = pipeline.translate(
PathBuf::from("multi.py"),
source.to_string(),
).await;
match result {
Ok(output) => {
assert!(output.rust_code.contains("add"));
assert!(output.rust_code.contains("multiply"));
}
Err(e) => {
println!("Pipeline error (expected if wasm32 target not installed): {}", e);
}
}
}
#[test]
fn test_pipeline_state_tracking() {
let pipeline = Pipeline::new();
assert_eq!(pipeline.state().phase, Phase::Idle);
}
}