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"
"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"
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"
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"
"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"
"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"
version = "0.1.0"
dependencies = [
"anyhow",
+ "askama",
"clap",
"inquire",
]
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"
+use askama::Template;
use clap::Parser;
use inquire::{Confirm, CustomType, Select, Text};
use std::{
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()?;
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")),
&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)?,
),
];
}
}
-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(
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()
}