]> git.taranathan.com Git - subsystem-generator.git/commitdiff
use askama
authormoo <moogoesmeow123@gmail.com>
Sun, 10 May 2026 21:33:19 +0000 (14:33 -0700)
committermoo <moogoesmeow123@gmail.com>
Sun, 10 May 2026 21:33:19 +0000 (14:33 -0700)
Cargo.lock
Cargo.toml
README.md
src/main.rs

index f4408e7a147282c751c9b7bdd73ed19495baa8fa..1a885a27351102f5616d1abcbcabdf48cdce3675 100644 (file)
@@ -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"
index 8efed6b28bac209c1e9aaf4453e3497e160660bd..63e93e60033042e8631bcd0d55b35d36407d7028 100644 (file)
@@ -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"
index 1beaafbb33a6889e11b8c78c3cc54f00a08faa51..8212c2d4116fab8d5b16fde6c5e24a5ae748442d 100644 (file)
--- 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/`.
index ce59644d42332590970067340a249a2173d00a7a..bdeb54695db129fe53662781e5811a192669c15f 100644 (file)
@@ -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<MotorConfig>,
 }
 
+#[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<dyn std::error::Error>> {
     let args = Args::parse();
     let mut config = prompt_config()?;
@@ -50,20 +99,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     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<dyn std::error::Error>> {
                 &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<MotorTemplate> {
+    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<String, askama::Error> {
+    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<String, askama::Error> {
+    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<String, askama::Error> {
+    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<String, askama::Error> {
+    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()
 }