From ccf802886391a91f9ccee01ddd10d67ab33168d4 Mon Sep 17 00:00:00 2001 From: moo Date: Sun, 10 May 2026 14:33:19 -0700 Subject: [PATCH] use askama --- Cargo.lock | 150 ++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 1 + src/main.rs | 324 +++++++++++++++++++--------------------------------- 4 files changed, 267 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4408e7..1a885a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,68 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "askama" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc" +dependencies = [ + "askama_macros", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738" +dependencies = [ + "askama_parser", + "basic-toml", + "glob", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a" +dependencies = [ + "askama_derive", +] + +[[package]] +name = "askama_parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" +dependencies = [ + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow", +] + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -187,6 +249,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "heck" version = "0.5.0" @@ -216,6 +284,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "libc" version = "0.2.186" @@ -237,6 +311,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + [[package]] name = "mio" version = "0.8.11" @@ -293,6 +373,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -320,12 +406,60 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "signal-hook" version = "0.3.18" @@ -374,6 +508,7 @@ name = "subsystem-generator" version = "0.1.0" dependencies = [ "anyhow", + "askama", "clap", "inquire", ] @@ -530,3 +665,18 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 8efed6b..63e93e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ edition = "2024" [dependencies] anyhow = "1.0.102" +askama = "0.16.0" clap = { version = "4.6.1", features = ["derive"] } inquire = "0.7" diff --git a/README.md b/README.md index 1beaafb..8212c2d 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,4 @@ run with `cargo run`, should work +Java source generation now uses **Askama** templates stored in `templates/`. diff --git a/src/main.rs b/src/main.rs index ce59644..bdeb546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use askama::Template; use clap::Parser; use inquire::{Confirm, CustomType, Select, Text}; use std::{ @@ -32,6 +33,54 @@ struct GeneratorConfig { motors: Vec, } +#[derive(Debug)] +struct MotorTemplate { + field_base: String, + constant_base: String, + method_suffix: String, + can_id: i32, + inverted_value: &'static str, +} + +#[derive(Template)] +#[template(path = "constants.java.askama")] +struct ConstantsTemplate<'a> { + package: &'a str, + constants_class: &'a str, + motors: &'a [MotorTemplate], +} + +#[derive(Template)] +#[template(path = "io_interface.java.askama")] +struct IoInterfaceTemplate<'a> { + package: &'a str, + io_interface: &'a str, + io_inputs: &'a str, + motors: &'a [MotorTemplate], +} + +#[derive(Template)] +#[template(path = "io_impl.java.askama")] +struct IoImplTemplate<'a> { + package: &'a str, + io_impl: &'a str, + io_interface: &'a str, + io_inputs: &'a str, + constants_class: &'a str, + neutral_mode_value: &'a str, + motors: &'a [MotorTemplate], +} + +#[derive(Template)] +#[template(path = "subsystem.java.askama")] +struct SubsystemTemplate<'a> { + package: &'a str, + subsystem: &'a str, + io_interface: &'a str, + io_inputs_auto: String, + motors: &'a [MotorTemplate], +} + fn main() -> Result<(), Box> { let args = Args::parse(); let mut config = prompt_config()?; @@ -50,20 +99,15 @@ fn main() -> Result<(), Box> { let io_inputs = format!("{}IOInputs", config.subsystem); let constants_class = format!("{}Constants", config.subsystem); + let template_motors = to_template_motors(&config.motors); let files = vec![ ( subsystem_dir.join(format!("{constants_class}.java")), - render_constants(&package, &constants_class, &config.motors), + render_constants(&package, &constants_class, &template_motors)?, ), ( subsystem_dir.join(format!("{io_interface}.java")), - render_io_interface( - &package, - &io_interface, - &io_inputs, - &constants_class, - &config.motors, - ), + render_io_interface(&package, &io_interface, &io_inputs, &template_motors)?, ), ( subsystem_dir.join(format!("{io_impl}.java")), @@ -74,12 +118,12 @@ fn main() -> Result<(), Box> { &io_inputs, &constants_class, &config.neutral_mode, - &config.motors, - ), + &template_motors, + )?, ), ( subsystem_dir.join(format!("{}.java", config.subsystem)), - render_subsystem(&package, &config.subsystem, &io_interface, &config.motors), + render_subsystem(&package, &config.subsystem, &io_interface, &template_motors)?, ), ]; @@ -285,75 +329,49 @@ fn upper_first(value: &str) -> String { } } -fn render_constants(package: &str, constants_class: &str, motors: &[MotorConfig]) -> String { - let mut motor_constants = String::new(); - for motor in motors { - motor_constants.push_str(&format!( - " public static final int {}_MOTOR_ID = {};\n", - motor.constant_base, motor.can_id - )); - } - - format!( - "package {package}; - -public class {constants_class} {{ +fn to_template_motors(motors: &[MotorConfig]) -> Vec { + motors + .iter() + .map(|motor| MotorTemplate { + field_base: motor.field_base.clone(), + constant_base: motor.constant_base.clone(), + method_suffix: upper_first(&motor.field_base), + can_id: motor.can_id, + inverted_value: if motor.inverted { + "InvertedValue.Clockwise_Positive" + } else { + "InvertedValue.CounterClockwise_Positive" + }, + }) + .collect() +} -{motor_constants}}} -" - ) +fn render_constants( + package: &str, + constants_class: &str, + motors: &[MotorTemplate], +) -> Result { + ConstantsTemplate { + package, + constants_class, + motors, + } + .render() } fn render_io_interface( package: &str, io_interface: &str, io_inputs: &str, - _constants_class: &str, - motors: &[MotorConfig], -) -> String { - let mut input_fields = String::new(); - for motor in motors { - let f = &motor.field_base; - input_fields.push_str(&format!(" public double {f}PositionRot = 0.0;\n")); - input_fields.push_str(&format!(" public double {f}VelocityRps = 0.0;\n")); - input_fields.push_str(&format!(" public double {f}StatorCurrentAmps = 0.0;\n")); - input_fields.push_str(&format!(" public double {f}SupplyCurrentAmps = 0.0;\n")); - input_fields.push_str(&format!(" public double {f}AppliedVolts = 0.0;\n")); - } - - let mut speed_methods = String::new(); - let mut stop_calls = String::new(); - for motor in motors { - let method_suffix = upper_first(&motor.field_base); - speed_methods.push_str(&format!( - " public void set{method_suffix}SpeedRaw(double speed);\n\n" - )); - speed_methods.push_str(&format!( - " public void set{method_suffix}Control(ControlRequest request);\n\n" - )); - stop_calls.push_str(&format!(" set{method_suffix}SpeedRaw(0.0);\n")); + motors: &[MotorTemplate], +) -> Result { + IoInterfaceTemplate { + package, + io_interface, + io_inputs, + motors, } - - format!( - "package {package}; - -import org.littletonrobotics.junction.AutoLog; -import com.ctre.phoenix6.controls.ControlRequest; - -public interface {io_interface} {{ - @AutoLog - public static class {io_inputs} {{ -{input_fields} }} - - public void updateInputs({io_inputs} inputs); - -{speed_methods} public default void stop() {{ -{stop_calls} }} - - public default void close() {{}} -}} -" - ) + .render() } fn render_io_impl( @@ -363,149 +381,37 @@ fn render_io_impl( io_inputs: &str, constants_class: &str, neutral_mode: &str, - motors: &[MotorConfig], -) -> String { - let mut motor_fields = String::new(); - for motor in motors { - motor_fields.push_str(&format!( - " private final TalonFX {}Motor = new TalonFX({constants_class}.{}_MOTOR_ID, Constants.CANIVORE_SUB);\n", - motor.field_base, motor.constant_base - )); - } - - let mut config_calls = String::new(); - for motor in motors { - let inverted = if motor.inverted { - "InvertedValue.Clockwise_Positive" - } else { - "InvertedValue.CounterClockwise_Positive" - }; - let neutral = if neutral_mode == "Brake" { - "NeutralModeValue.Brake" - } else { - "NeutralModeValue.Coast" - }; - config_calls.push_str(&format!( - " {}Motor.getConfigurator().apply(config);\n", - motor.field_base - )); - config_calls.push_str(&format!( - " {}Motor.getConfigurator().apply(new MotorOutputConfigs().withInverted({inverted}).withNeutralMode({neutral}));\n", - motor.field_base - )); - } - - let mut input_assignments = String::new(); - for motor in motors { - let f = &motor.field_base; - input_assignments.push_str(&format!( - " inputs.{f}PositionRot = {f}Motor.getPosition().getValueAsDouble();\n" - )); - input_assignments.push_str(&format!( - " inputs.{f}VelocityRps = {f}Motor.getVelocity().getValueAsDouble();\n" - )); - input_assignments.push_str(&format!( - " inputs.{f}StatorCurrentAmps = {f}Motor.getStatorCurrent().getValueAsDouble();\n" - )); - input_assignments.push_str(&format!( - " inputs.{f}SupplyCurrentAmps = {f}Motor.getSupplyCurrent().getValueAsDouble();\n" - )); - input_assignments.push_str(&format!( - " inputs.{f}AppliedVolts = {f}Motor.getMotorVoltage().getValueAsDouble();\n" - )); - } - - let mut raw_speed_methods = String::new(); - for motor in motors { - let method_suffix = upper_first(&motor.field_base); - raw_speed_methods.push_str(&format!( - " @Override\n public void set{method_suffix}SpeedRaw(double speed) {{\n {}Motor.set(speed);\n }}\n\n", - motor.field_base - )); - raw_speed_methods.push_str(&format!( - " @Override\n public void set{method_suffix}Control(ControlRequest request) {{\n {}Motor.setControl(request);\n }}\n\n", - motor.field_base - )); - } - - let mut close_calls = String::new(); - for motor in motors { - close_calls.push_str(&format!(" {}Motor.close();\n", motor.field_base)); + motors: &[MotorTemplate], +) -> Result { + let neutral_mode_value = if neutral_mode == "Brake" { + "NeutralModeValue.Brake" + } else { + "NeutralModeValue.Coast" + }; + IoImplTemplate { + package, + io_impl, + io_interface, + io_inputs, + constants_class, + neutral_mode_value, + motors, } - - format!( - "package {package}; - -import com.ctre.phoenix6.configs.MotorOutputConfigs; -import com.ctre.phoenix6.configs.TalonFXConfiguration; -import com.ctre.phoenix6.controls.ControlRequest; -import com.ctre.phoenix6.hardware.TalonFX; -import com.ctre.phoenix6.signals.InvertedValue; -import com.ctre.phoenix6.signals.NeutralModeValue; -import frc.robot.constants.Constants; - -public class {io_impl} implements {io_interface} {{ -{motor_fields} - public {io_impl}() {{ - TalonFXConfiguration config = new TalonFXConfiguration(); - // TODO: tune PID, current limits, and motion magic limits -{config_calls} }} - - @Override - public void updateInputs({io_inputs} inputs) {{ -{input_assignments} }} - -{raw_speed_methods} @Override - public void close() {{ -{close_calls} }} -}} -" - ) + .render() } fn render_subsystem( package: &str, subsystem: &str, io_interface: &str, - motors: &[MotorConfig], -) -> String { - let io_inputs_auto = format!("{subsystem}IOInputsAutoLogged"); - let mut methods = String::new(); - for motor in motors { - let method_suffix = upper_first(&motor.field_base); - methods.push_str(&format!( - " public void set{method_suffix}SpeedRaw(double speed) {{\n io.set{method_suffix}SpeedRaw(speed);\n }}\n\n" - )); + motors: &[MotorTemplate], +) -> Result { + SubsystemTemplate { + package, + subsystem, + io_interface, + io_inputs_auto: format!("{subsystem}IOInputsAutoLogged"), + motors, } - format!( - "package {package}; - -import org.littletonrobotics.junction.Logger; - -import edu.wpi.first.wpilibj2.command.SubsystemBase; - -public class {subsystem} extends SubsystemBase {{ - private final {io_interface} io; - private final {io_inputs_auto} inputs = new {io_inputs_auto}(); - - public {subsystem}({io_interface} io) {{ - this.io = io; - }} - - @Override - public void periodic() {{ - io.updateInputs(inputs); - Logger.processInputs(\"{subsystem}\", inputs); - }} - -{methods} public void stop() {{ - io.stop(); - }} - - public void close() {{ - io.close(); - }} -}} -" - ) + .render() } -- 2.39.5