feat(ariadne): add pipeline skeleton
This commit is contained in:
@@ -3,7 +3,10 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
|
|||||||
rust_library(
|
rust_library(
|
||||||
name = "ariadne_lib",
|
name = "ariadne_lib",
|
||||||
crate_name = "ariadne",
|
crate_name = "ariadne",
|
||||||
srcs = ["lib.rs"],
|
srcs = [
|
||||||
|
"lib.rs",
|
||||||
|
"pipeline.rs",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
rust_binary(
|
rust_binary(
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::pipeline::Stage;
|
||||||
|
|
||||||
|
pub mod pipeline;
|
||||||
|
|
||||||
|
const OUTPUT_GRAPH_DIR: &str = "graph";
|
||||||
|
|
||||||
pub type Result<T> = io::Result<T>;
|
pub type Result<T> = io::Result<T>;
|
||||||
|
|
||||||
@@ -9,99 +14,46 @@ pub type Result<T> = io::Result<T>;
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub output_dir: PathBuf,
|
pub output_dir: PathBuf,
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
}
|
pub target_stage: pipeline::Stage,
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new(output_dir: PathBuf, force: bool) -> Self {
|
|
||||||
Self { output_dir, force }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
|
||||||
pub enum Stage {
|
|
||||||
Parse,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stage {
|
|
||||||
pub const ALL: &'static [Stage] = &[Stage::Parse];
|
|
||||||
|
|
||||||
pub fn name(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Stage::Parse => "parse",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prerequisites(self) -> &'static [Stage] {
|
|
||||||
match self {
|
|
||||||
Stage::Parse => &[],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(config: &Config) -> Result<()> {
|
pub fn run(config: &Config) -> Result<()> {
|
||||||
let stdout = io::stdout();
|
for stage in config.target_stage.with_prerequisites() {
|
||||||
let mut handle = stdout.lock();
|
run_stage(config, stage)?;
|
||||||
run_with_writer(config, &mut handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_with_writer(config: &Config, writer: &mut dyn Write) -> Result<()> {
|
|
||||||
for stage in plan_stages(Stage::ALL) {
|
|
||||||
run_stage(stage, config, writer)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plan_stages(stages: &[Stage]) -> Vec<Stage> {
|
fn get_output_dir(config: &Config, stage: Stage) -> Result<PathBuf> {
|
||||||
fn visit(stage: Stage, seen: &mut HashSet<Stage>, planned: &mut Vec<Stage>) {
|
let output_dir = config.output_dir.join(OUTPUT_GRAPH_DIR).join(stage.name());
|
||||||
if !seen.insert(stage) {
|
|
||||||
return;
|
if output_dir.exists() && !output_dir.is_dir() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::NotADirectory,
|
||||||
|
format!("output path is not a directory: {path}", path = output_dir.display()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if output_dir.is_dir() && fs::read_dir(&output_dir)?.next().transpose()?.is_some() {
|
||||||
|
if !config.force {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::AlreadyExists,
|
||||||
|
format!(
|
||||||
|
"output directory already exists and is not empty: {path}",
|
||||||
|
path = output_dir.display()
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
for prerequisite in stage.prerequisites() {
|
|
||||||
visit(*prerequisite, seen, planned);
|
fs::remove_dir_all(&output_dir)?;
|
||||||
}
|
|
||||||
planned.push(stage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut seen = HashSet::new();
|
fs::create_dir_all(&output_dir)?;
|
||||||
let mut planned = Vec::new();
|
Ok(output_dir)
|
||||||
for stage in stages {
|
|
||||||
visit(*stage, &mut seen, &mut planned);
|
|
||||||
}
|
|
||||||
planned
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_stage(stage: Stage, config: &Config, writer: &mut dyn Write) -> Result<()> {
|
fn run_stage(config: &Config, stage: Stage) -> Result<()> {
|
||||||
let stage_dir = stage_dir(&config.output_dir, stage);
|
let output_dir = get_output_dir(config, stage)?;
|
||||||
|
pipeline::run_stage(stage, &output_dir)
|
||||||
if stage_dir.exists() {
|
|
||||||
if config.force {
|
|
||||||
fs::remove_dir_all(&stage_dir)?;
|
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"SKIP {}: output exists at {}",
|
|
||||||
stage.name(),
|
|
||||||
stage_dir.display()
|
|
||||||
)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(writer, "RUN {}", stage.name())?;
|
|
||||||
match stage {
|
|
||||||
Stage::Parse => run_parse(&stage_dir, writer)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stage_dir(output_dir: &Path, stage: Stage) -> PathBuf {
|
|
||||||
output_dir.join("graph").join(stage.name())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_parse(stage_dir: &Path, writer: &mut dyn Write) -> Result<()> {
|
|
||||||
fs::create_dir_all(stage_dir)?;
|
|
||||||
let summary_path = stage_dir.join("summary.txt");
|
|
||||||
fs::write(&summary_path, "parse stage placeholder\n")?;
|
|
||||||
writeln!(writer, "WRITE {}", summary_path.display())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,51 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::ExitCode;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "ariadne", about = "Analyze Clew output for a Ren'Py game.")]
|
struct Args {
|
||||||
struct Cli {
|
|
||||||
#[arg(long, default_value = "output")]
|
#[arg(long, default_value = "output")]
|
||||||
output_dir: PathBuf,
|
output_dir: PathBuf,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
force: bool,
|
force: bool,
|
||||||
|
|
||||||
|
#[arg(default_value = "all", value_parser = parse_stage)]
|
||||||
|
target_stage: ariadne::pipeline::Stage,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
impl From<Args> for ariadne::Config {
|
||||||
let cli = Cli::parse();
|
fn from(args: Args) -> Self {
|
||||||
let config = ariadne::Config::new(cli.output_dir, cli.force);
|
ariadne::Config {
|
||||||
|
output_dir: resolve_workspace_path(args.output_dir),
|
||||||
match ariadne::run(&config) {
|
force: args.force,
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
target_stage: args.target_stage,
|
||||||
Err(error) => {
|
|
||||||
eprintln!("ERROR: {error}");
|
|
||||||
ExitCode::FAILURE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_stage(value: &str) -> Result<ariadne::pipeline::Stage, String> {
|
||||||
|
ariadne::pipeline::Stage::ALL
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|stage| stage.name() == value)
|
||||||
|
.ok_or_else(|| format!("unknown stage: {value}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move this into a shared Rust utility crate.
|
||||||
|
fn resolve_workspace_path(path: PathBuf) -> PathBuf {
|
||||||
|
if path.is_absolute() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(workspace) = std::env::var_os("BUILD_WORKSPACE_DIRECTORY") {
|
||||||
|
return PathBuf::from(workspace).join(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> ariadne::Result<()> {
|
||||||
|
let config: ariadne::Config = Args::parse().into();
|
||||||
|
ariadne::run(&config)
|
||||||
|
}
|
||||||
|
|||||||
101
src/ariadne/pipeline.rs
Normal file
101
src/ariadne/pipeline.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub enum Stage {
|
||||||
|
#[default]
|
||||||
|
All,
|
||||||
|
|
||||||
|
Parse,
|
||||||
|
AddImplicitEdges,
|
||||||
|
AddInitEdges,
|
||||||
|
AddJumpEdges,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stage {
|
||||||
|
pub const ALL: &'static [Stage] = &[
|
||||||
|
Stage::All,
|
||||||
|
Stage::Parse,
|
||||||
|
Stage::AddImplicitEdges,
|
||||||
|
Stage::AddInitEdges,
|
||||||
|
Stage::AddJumpEdges,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Stage::All => "all",
|
||||||
|
Stage::Parse => "parse",
|
||||||
|
Stage::AddImplicitEdges => "add-implicit-edges",
|
||||||
|
Stage::AddInitEdges => "add-init-edges",
|
||||||
|
Stage::AddJumpEdges => "add-jump-edges",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prerequisites(self) -> &'static [Stage] {
|
||||||
|
match self {
|
||||||
|
Stage::All => &[
|
||||||
|
Stage::AddImplicitEdges,
|
||||||
|
Stage::AddInitEdges,
|
||||||
|
Stage::AddJumpEdges,
|
||||||
|
],
|
||||||
|
Stage::Parse => &[],
|
||||||
|
Stage::AddImplicitEdges => &[Stage::Parse],
|
||||||
|
Stage::AddInitEdges => &[Stage::Parse],
|
||||||
|
Stage::AddJumpEdges => &[Stage::Parse],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_prerequisites(self) -> Vec<Stage> {
|
||||||
|
self.with_prerequisites_impl(BTreeSet::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_prerequisites_impl(self, seen: BTreeSet<Stage>) -> Vec<Stage> {
|
||||||
|
if seen.contains(&self) {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut seen = seen;
|
||||||
|
seen.insert(self);
|
||||||
|
|
||||||
|
let mut stages = Vec::new();
|
||||||
|
for prerequisite in self.prerequisites() {
|
||||||
|
let prerequisite_stages = prerequisite.with_prerequisites_impl(seen.clone());
|
||||||
|
seen.extend(prerequisite_stages.iter().copied());
|
||||||
|
stages.extend(prerequisite_stages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self != Stage::All {
|
||||||
|
stages.push(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
stages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_stage(stage: Stage, output_dir: &Path) -> Result<()> {
|
||||||
|
match stage {
|
||||||
|
Stage::All => Ok(()),
|
||||||
|
Stage::Parse => run_parse(output_dir),
|
||||||
|
Stage::AddImplicitEdges => run_add_implicit_edges(output_dir),
|
||||||
|
Stage::AddInitEdges => run_add_init_edges(output_dir),
|
||||||
|
Stage::AddJumpEdges => run_add_jump_edges(output_dir),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_parse(_output_dir: &Path) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_add_implicit_edges(_output_dir: &Path) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_add_init_edges(_output_dir: &Path) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_add_jump_edges(_output_dir: &Path) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user