From 751dd0de501e87dfbb9ffab527a7c0e58ad715a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 15 Jul 2021 20:20:40 +0300 Subject: [PATCH 1/5] Fix parser build --- tools/kicad/kicad_rs/src/bin/parser.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/kicad/kicad_rs/src/bin/parser.rs b/tools/kicad/kicad_rs/src/bin/parser.rs index c6f4d8a..ce0f872 100644 --- a/tools/kicad/kicad_rs/src/bin/parser.rs +++ b/tools/kicad/kicad_rs/src/bin/parser.rs @@ -1,6 +1,6 @@ use kicad_rs::codec; use kicad_rs::error::DynamicResult; -use kicad_rs::types::*; +use kicad_rs::parser::SchematicTree; use std::env; use std::io; use std::path::Path; @@ -15,7 +15,8 @@ fn main() -> DynamicResult<()> { ); // Parse the schematic file - let sch = Schematic::parse(&p)?; + let tree = SchematicTree::load(&p)?; + let sch = tree.parse()?; // Marshal as YAML codec::marshal_yaml(&sch, io::stdout())?; From d3dd43a109edbe33fc451b4fd585a3b3c72b18c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 15 Jul 2021 20:23:30 +0300 Subject: [PATCH 2/5] Use HashMaps instead of vectors in our API types --- tools/kicad/kicad_rs/src/eval.rs | 32 +++++++++---- tools/kicad/kicad_rs/src/eval/entry.rs | 10 ++-- tools/kicad/kicad_rs/src/parser.rs | 59 +++++++++++------------- tools/kicad/kicad_rs/src/requirements.rs | 7 +-- tools/kicad/kicad_rs/src/types.rs | 31 +++++++------ 5 files changed, 76 insertions(+), 63 deletions(-) diff --git a/tools/kicad/kicad_rs/src/eval.rs b/tools/kicad/kicad_rs/src/eval.rs index 3af7803..ecfa3a8 100644 --- a/tools/kicad/kicad_rs/src/eval.rs +++ b/tools/kicad/kicad_rs/src/eval.rs @@ -8,34 +8,35 @@ use crate::eval::path::Path; use crate::types::Schematic; pub fn index_schematic(sch: &mut Schematic) -> DynamicResult { + // TODO: Revisit the SheetIndex idea now that Schematic contains HashMaps? let mut index = SheetIndex::new(); - for c in sch.components.iter_mut() { + for (_, c) in sch.components.iter_mut() { let mut component_idx = ComponentIndex::new(); - for a in c.attributes.iter_mut() { - if component_idx.contains_key(&a.name) { + for (attr_name, a) in c.attributes.iter_mut() { + if component_idx.contains_key(attr_name) { return Err(errorf(&format!( "duplicate attribute definition: {}", - a.name + attr_name ))); } - component_idx.insert(a.name.clone(), a.into()); + component_idx.insert(attr_name.into(), (attr_name, a).into()); } index .map .insert(c.labels.reference.clone(), Node::Component(component_idx)); } - for sub_sch in sch.sub_schematics.iter_mut() { - if index.map.contains_key(&sub_sch.id) { + for (sch_id, sub_sch) in sch.sub_schematics.iter_mut() { + if index.map.contains_key(sch_id) { return Err(errorf(&format!( "component and schematic name collision: {}", - sub_sch.id + sch_id ))); } index .map - .insert(sub_sch.id.clone(), Node::Sheet(index_schematic(sub_sch)?)); + .insert(sch_id.into(), Node::Sheet(index_schematic(sub_sch)?)); } Ok(index) @@ -68,9 +69,20 @@ pub fn evaluate_schematic(index: &mut SheetIndex) -> DynamicResult<()> { } fn evaluate(idx: &mut SheetIndex, p: &Path) -> DynamicResult<()> { + // TODO: Support u, k, M, G, etc. suffixes. Now the evaluator treats them as a + // variable. + // TODO: Support case-insensitive referencing of attributes (e.g. C3.Value == C3.value)? + // TODO: Decide whether we should write out the unit too in the value or not, e.g. + // "35" vs "35 F". "35 F" looks nicer in KiCad, but also might mess up the parsing unless + // we have a well-known "undo" method like stripping the " {}" suffix where {} is the unit + // before parsing the rest of the string into a float or string. + // TODO: An expression like "R7.Value/500" will perform integer division if both expressions + // resolve to an integer, which is something to be aware of. Additionally, it seems like putting + // just "500.0" in an expression resolves to "500" in the output, something which might be + // desired, but just worth documenting. let entry = idx .resolve_entry(p.iter()) - .ok_or(errorf("entry not found"))?; + .ok_or(errorf(&format!("entry not found: {}", p)))?; if entry.value_defined()? { return Ok(()); // Don't update if already set diff --git a/tools/kicad/kicad_rs/src/eval/entry.rs b/tools/kicad/kicad_rs/src/eval/entry.rs index d45eec2..ccd1e30 100644 --- a/tools/kicad/kicad_rs/src/eval/entry.rs +++ b/tools/kicad/kicad_rs/src/eval/entry.rs @@ -7,6 +7,7 @@ use std::cell::RefCell; #[derive(Debug)] pub struct Entry<'a> { set_in_progress: RefCell, + attr_name: &'a String, attribute: &'a mut Attribute, value: Option, } @@ -26,7 +27,7 @@ fn expected_type(expected_type: &ValueType, actual: Value) -> EvalexprError { impl<'a> Entry<'a> { pub fn get_name(&self) -> &str { - &self.attribute.name + self.attr_name } pub fn get_expression(&self) -> &str { @@ -67,11 +68,12 @@ impl<'a> Entry<'a> { } } -impl<'a> From<&'a mut Attribute> for Entry<'a> { - fn from(attribute: &'a mut Attribute) -> Self { +impl<'a> From<(&'a String, &'a mut Attribute)> for Entry<'a> { + fn from(attr_tuple: (&'a String, &'a mut Attribute)) -> Self { Self { set_in_progress: RefCell::new(false), - attribute, + attr_name: attr_tuple.0, + attribute: attr_tuple.1, value: None, } } diff --git a/tools/kicad/kicad_rs/src/parser.rs b/tools/kicad/kicad_rs/src/parser.rs index 2ed6db6..2430759 100644 --- a/tools/kicad/kicad_rs/src/parser.rs +++ b/tools/kicad/kicad_rs/src/parser.rs @@ -31,18 +31,18 @@ impl SchematicTree { // Parse the SchematicTree into our own nested Schematic struct pub fn parse(&self) -> DynamicResult { - parse_schematic(self, String::new()) + parse_schematic(self) } // Update the components in the kicad_parse_gen Schematic tree using the given // nested Schematic struct (copy values from Attributes to ComponentFields) pub fn update(&mut self, schematic: &Schematic) -> DynamicResult<()> { // Update the fields of all components in this schematic - for component in schematic.components.iter() { + for (_, component) in schematic.components.iter() { self.schematic .modify_component(&component.labels.reference, |c| { - for attribute in component.attributes.iter() { - let name = attribute.name.as_str().or_default("Value"); + for (attr_name, attribute) in component.attributes.iter() { + let name = attr_name.as_str().or_default("Value"); c.update_field(name, &attribute.value.to_string()); c.update_field( &format!("{}{}", name, "_expr"), @@ -55,12 +55,12 @@ impl SchematicTree { } // Recursively update sub-schematics - for sub_schematic in schematic.sub_schematics.iter() { - match self.sub_schematics.get_mut(&sub_schematic.id) { + for (sch_id, sub_schematic) in schematic.sub_schematics.iter() { + match self.sub_schematics.get_mut(sch_id) { None => { return Err(errorf(&format!( "unknown sub-schematic: {}", - sub_schematic.id + sch_id ))) } Some(sub_tree) => sub_tree.update(sub_schematic)?, @@ -86,7 +86,7 @@ impl SchematicTree { } /// Turns the given KiCad schematic into a recursive Schematic struct -fn parse_schematic(file: &SchematicTree, id: String) -> DynamicResult { +fn parse_schematic(file: &SchematicTree) -> DynamicResult { // Parse the fields for the schematic let meta = parse_meta(&file.schematic)?; let globals = parse_globals(&file.schematic)?; @@ -95,7 +95,6 @@ fn parse_schematic(file: &SchematicTree, id: String) -> DynamicResult // Construct and return the parsed schematic Ok(Schematic { - id, meta, globals, components, @@ -133,8 +132,8 @@ fn parse_meta(kicad_sch: &kicad_schematic::Schematic) -> DynamicResult DynamicResult> { - let mut globals = Vec::new(); +fn parse_globals(kicad_sch: &kicad_schematic::Schematic) -> DynamicResult> { + let mut globals = HashMap::new(); // Loop through the elements of the schematic, which includes text notes as well for el in &kicad_sch.elements { @@ -175,13 +174,12 @@ fn parse_globals(kicad_sch: &kicad_schematic::Schematic) -> DynamicResult DynamicResult DynamicResult> { - let mut components = Vec::new(); +fn parse_components(kicad_sch: &kicad_schematic::Schematic) -> DynamicResult> { + let mut components = HashMap::new(); // Walk through all components in the sheet for comp in kicad_sch.components() { @@ -216,7 +214,8 @@ fn parse_components(kicad_sch: &kicad_schematic::Schematic) -> DynamicResult DynamicResult DynamicResult DynamicResult DynamicResult> { - let mut sub_schematics = Vec::new(); +fn parse_sub_schematics(tree: &SchematicTree) -> DynamicResult> { + let mut sub_schematics = HashMap::new(); // Recursively traverse and parse the sub-schematics for (id, schematic) in tree.sub_schematics.iter() { - sub_schematics.push(parse_schematic(schematic, id.clone())?); + sub_schematics.insert(id.into(), parse_schematic(schematic)?); } Ok(sub_schematics) diff --git a/tools/kicad/kicad_rs/src/requirements.rs b/tools/kicad/kicad_rs/src/requirements.rs index 1403eab..e868d8f 100644 --- a/tools/kicad/kicad_rs/src/requirements.rs +++ b/tools/kicad/kicad_rs/src/requirements.rs @@ -1,5 +1,6 @@ use crate::labels::{Labels, LabelsMatch}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; // Requirement specifies a requirement for a Labels key-value set. // The operator is the enum value, and the data to use for matching @@ -25,10 +26,10 @@ pub enum Requirement { Lt { key: String, values: [f64; 1] }, } -// Implement the LabelsMatch trait for a vector of requirements ANDed together -impl LabelsMatch for Vec { +// Implement the LabelsMatch trait for a HashMap of requirements ANDed together +impl LabelsMatch for HashMap { fn matches(&self, labels: &L) -> bool { - self.iter().all(|r| r.matches(labels)) + self.iter().all(|r| r.1.matches(labels)) } } diff --git a/tools/kicad/kicad_rs/src/types.rs b/tools/kicad/kicad_rs/src/types.rs index a93eda9..3015492 100644 --- a/tools/kicad/kicad_rs/src/types.rs +++ b/tools/kicad/kicad_rs/src/types.rs @@ -10,18 +10,16 @@ use crate::labels::Labels; #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct Schematic { - // The "top-level" schematic has id "" - pub id: String, pub meta: SchematicMeta, - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(default)] - pub globals: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] + pub globals: HashMap, + #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(default)] - pub components: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] + pub components: HashMap, + #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(default)] - pub sub_schematics: Vec, + pub sub_schematics: HashMap, } #[derive(Serialize, Deserialize, Debug)] @@ -56,9 +54,15 @@ pub struct Component { #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] pub classes: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] + pub attributes: HashMap, + + // Disregard everything in this field by never serializing it, but allowing + // to deserialize (to avoid an "unknown fields" error). + #[serde(skip_serializing)] #[serde(default)] - pub attributes: Vec, + pub generated: serde_json::Value, } #[derive(Serialize, Deserialize, Debug)] @@ -97,7 +101,6 @@ impl ComponentLabels { #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct Attribute { - pub name: String, #[serde(flatten)] pub value: Value, pub expression: String, @@ -154,10 +157,8 @@ impl From for Value { } // A vector of Attributes implements the Labels trait -impl Labels for Vec { +impl Labels for HashMap { fn get_label(&self, key: &str) -> Option { - self.iter() - .find(|&a| a.name == key) - .map(|a| a.value.to_string()) + self.get(key).map(|a| a.value.to_string()) } } From 35491f9f8ec413e758c6690f68759a93410807ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 15 Jul 2021 20:24:19 +0300 Subject: [PATCH 3/5] Implement the policy enforcement and defaulting engine using CUE --- tools/kicad/kicad_rs/Cargo.toml | 1 + tools/kicad/kicad_rs/sample_policy.cue | 62 ++++++ tools/kicad/kicad_rs/src/bin/classifier.rs | 12 +- tools/kicad/kicad_rs/src/classifier.rs | 80 ------- tools/kicad/kicad_rs/src/cue/common.cue | 40 ++++ tools/kicad/kicad_rs/src/cue/map.cue | 33 +++ .../kicad/kicad_rs/src/cue/policy_schema.cue | 26 +++ tools/kicad/kicad_rs/src/cue/reduce.cue | 13 ++ tools/kicad/kicad_rs/src/lib.rs | 2 +- tools/kicad/kicad_rs/src/policy.rs | 197 ++++++++++++++++++ 10 files changed, 377 insertions(+), 89 deletions(-) create mode 100644 tools/kicad/kicad_rs/sample_policy.cue delete mode 100644 tools/kicad/kicad_rs/src/classifier.rs create mode 100644 tools/kicad/kicad_rs/src/cue/common.cue create mode 100644 tools/kicad/kicad_rs/src/cue/map.cue create mode 100644 tools/kicad/kicad_rs/src/cue/policy_schema.cue create mode 100644 tools/kicad/kicad_rs/src/cue/reduce.cue create mode 100644 tools/kicad/kicad_rs/src/policy.rs diff --git a/tools/kicad/kicad_rs/Cargo.toml b/tools/kicad/kicad_rs/Cargo.toml index bec3c9a..a88784e 100644 --- a/tools/kicad/kicad_rs/Cargo.toml +++ b/tools/kicad/kicad_rs/Cargo.toml @@ -13,3 +13,4 @@ kicad_parse_gen = { git = "https://github.com/productize/kicad-parse-gen", branc serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.8" +tempfile = "3.2" diff --git a/tools/kicad/kicad_rs/sample_policy.cue b/tools/kicad/kicad_rs/sample_policy.cue new file mode 100644 index 0000000..9618ea6 --- /dev/null +++ b/tools/kicad/kicad_rs/sample_policy.cue @@ -0,0 +1,62 @@ +// This is a sample policy file that can be passed to the classifier + +// Define all classifiers +#Classifiers: [{ + class: "capacitor" + // Note: "c_symbol" is a user-defined key and only used for object merging when extending + labels: c_symbol: { + key: "symbolName" + op: "In" + values: ["C_Small", "C_Large"] + } +}, { + class: "capacitor" + // Note: "footprint_equals_smd" is a user-defined key and only used for object merging + labels: footprint_equals_smd: { + key: "footprintLibrary" + op: "Equals" + values: ["Capacitor_SMD"] + } +}, _tmpl_resistor & { + class: "resistor" +}, _tmpl_resistor & { // shunt_resistor extends all the properties of resistor + class: "shunt_resistor" + attributes: { + lt_1: { + key: "tolerance" + op: "Lt" + values: [1.1] + } + } +}] + +// #Policy defines requirements and defaults for all components belonging to a class +#Policy: { + shunt_resistor: { + labels: { + extra: shunt: "true" + datasheet: "foo" + } + attributes: tolerance: { + value: <1.1 | string + unit: string | *"Ohm" + } + } + capacitor: attributes: Value: { + comment: string | *"This is a capacitor :D!" + } +} + +// Common "templates" +_tmpl_resistor: { + labels: r_in_symbol: { + key: "symbolName" + op: "In" + values: ["R_Small", "R_Large"] + } + attributes: lt_5: { + key: "tolerance" + op: "Lt" + values: [5.1] + } +} diff --git a/tools/kicad/kicad_rs/src/bin/classifier.rs b/tools/kicad/kicad_rs/src/bin/classifier.rs index a8352b7..bb4eddc 100644 --- a/tools/kicad/kicad_rs/src/bin/classifier.rs +++ b/tools/kicad/kicad_rs/src/bin/classifier.rs @@ -1,9 +1,8 @@ -use kicad_rs::classifier::*; use kicad_rs::codec; use kicad_rs::error::DynamicResult; +use kicad_rs::policy; use kicad_rs::types::Schematic; use std::env; -use std::fs; use std::io; use std::path::Path; @@ -18,13 +17,10 @@ fn main() -> DynamicResult<()> { .ok_or("expected policy file as first argument")?, ); - // Read the policy file - let policy: Policy = codec::unmarshal_yaml(fs::File::open(&p)?)?; - - // Transform the schematic using the defined policy - let sch = policy.apply(sch)?; + // Apply the policy in the given file + let processed_sch = policy::apply(&p, sch)?; // Marshal the resulting schematic as YAML - codec::marshal_yaml(&sch, io::stdout())?; + codec::marshal_yaml(&processed_sch, io::stdout())?; Ok(()) } diff --git a/tools/kicad/kicad_rs/src/classifier.rs b/tools/kicad/kicad_rs/src/classifier.rs deleted file mode 100644 index 31f9d58..0000000 --- a/tools/kicad/kicad_rs/src/classifier.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::error::DynamicResult; -use crate::labels::LabelsMatch; -use crate::requirements::Requirement; -use crate::types::{Component, Schematic}; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; -use std::hash::Hash; -use std::iter::FromIterator; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct Policy { - classifiers: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ComponentClassifier { - // The class that shall be applied to a component matching these requirements - pub class: String, - // Labels matching - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub labels: Vec, - // Attribute matching - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub attributes: Vec, -} - -impl Policy { - // apply applies the policy on a given Schematic - pub fn apply(&self, sch: Schematic) -> DynamicResult { - let mut sch = sch; - classify_components(&mut sch, &self.classifiers); - Ok(sch) - } -} - -// classify_components recursively walks through a Schematic, and assigns the Component.classes field -fn classify_components(sch: &mut Schematic, classifiers: &Vec) { - for comp in sch.components.iter_mut() { - comp.classes = classify_component(comp, classifiers); - } - for sch in sch.sub_schematics.iter_mut() { - classify_components(sch, classifiers); - } -} - -// classify_component returns a list of classes for a given component, given the set of classifiers -fn classify_component(comp: &Component, classifiers: &Vec) -> Vec { - // Map all classifiers to their name if the component matches the classifier - let matched_classes: Vec = classifiers - .iter() - .filter_map(|classifier| { - // Require that both all labels and attribute requirements match - if !classifier.labels.matches(&comp.labels.to_map()) { - return None; - } - if !classifier.attributes.matches(&comp.attributes) { - return None; - } - - // If we get all the way here, we have "matched" with this class. - return Some(classifier.class.clone()); - }) - .collect(); - - // As there might be many classifiers of the same name that have matched with a component, - // filter all duplicates - filter_duplicates(&matched_classes) -} - -// filter_duplicates inserts all items into a HashSet, and builds a new vector without any duplicates -fn filter_duplicates(list: &Vec) -> Vec { - let f: HashSet<&T> = HashSet::from_iter(list.iter()); - f.iter().map(|s| s.clone().to_owned()).collect() -} diff --git a/tools/kicad/kicad_rs/src/cue/common.cue b/tools/kicad/kicad_rs/src/cue/common.cue new file mode 100644 index 0000000..b9f4453 --- /dev/null +++ b/tools/kicad/kicad_rs/src/cue/common.cue @@ -0,0 +1,40 @@ +// This file defines common definitions for all the policy-related CUE files + +// This "schematic" field is coming from SchematicHolder in policy.rs, and is used to enforce a +// schema for the top-level object, which is of type schematic. Then, once this rule is in place, +// we can enforce subSchematics to also abide by the same schema. +schematic: #Schematic +#Schematic: { + // The most important field of stdin that should be validated is the components map. + // The components map uses the component reference as a key. + components: [string]: #Component + // Support nested schematics + subSchematics: [string]: #Schematic + // TODO: Maybe enforce other rules on globals etc,? + ... +} + +#Component: { + labels: #Labels + attributes: [string]: #Attribute + classes: [...string] +} + +#Attribute: { + value: string | float + expression: string + type: "Float" | "String" + unit?: string + comment?: string +} + +#Labels: { + footprintLibrary: string + footprintName: string + reference: string + symbolLibrary: string + symbolName: string + model?: string + datasheet?: string + extra: [string]: string +} diff --git a/tools/kicad/kicad_rs/src/cue/map.cue b/tools/kicad/kicad_rs/src/cue/map.cue new file mode 100644 index 0000000..eece8a7 --- /dev/null +++ b/tools/kicad/kicad_rs/src/cue/map.cue @@ -0,0 +1,33 @@ +// This file "maps" all attributes and labels for a component to a "generated" sub-field +// where both the class-defined requirements and defaults are merged with the set values of +// the component +// This file depends on common.cue + +#Component: { + labels: #Labels + attributes: [string]: #Attribute + classes: [...string] + + for comp_class in classes { + for pol_class, pol_class_spec in #Policy { + if pol_class == comp_class { + generated: "\(comp_class)": { + // Merge attributes from both the class spec and the component + for attr, attr_spec in pol_class_spec.attributes { + attributes: "\(attr)": attr_spec + } + for attr, attr_spec in attributes { + attributes: "\(attr)": attr_spec + } + // Merge labels from both the class spec and the component + for key, val in pol_class_spec.labels { + labels: "\(key)": val + } + for key, val in labels { + labels: "\(key)": val + } + } + } + } + } +} diff --git a/tools/kicad/kicad_rs/src/cue/policy_schema.cue b/tools/kicad/kicad_rs/src/cue/policy_schema.cue new file mode 100644 index 0000000..0ea1e80 --- /dev/null +++ b/tools/kicad/kicad_rs/src/cue/policy_schema.cue @@ -0,0 +1,26 @@ +// This file describes the schema of the CUE file given to the classifier +// This file depends on common.cue + +// The user-defined #Policy object specifies what attributes and labels are mandated +// for each class, along with any needed defaults +#Policy: [string]: { + attributes: [string]: #Attribute + labels: #Labels +} + +// The user-defined #Classifiers object specifies how various attributes are labels +// can be used to classify a component to belonging to a class. +#Classifiers: [...#Classifier] +#Classifier: { + class: string + labels?: #RequirementMap + attributes?: #RequirementMap +} + +// The requirement string can be arbitrarily defined, it's used to enable +// the merging of classes extending each other +#RequirementMap: [string]: { + key: string + op: string + values: [...string] | [float] +} diff --git a/tools/kicad/kicad_rs/src/cue/reduce.cue b/tools/kicad/kicad_rs/src/cue/reduce.cue new file mode 100644 index 0000000..74e1b80 --- /dev/null +++ b/tools/kicad/kicad_rs/src/cue/reduce.cue @@ -0,0 +1,13 @@ +// For each component: use the "generated" field as the input, and from that make sure +// that each class agree on all the attributes (i.e. the definitions of labels and attributes +// will be merged for each class, and there must not be any conflicts). +#Component: { + generated: [string]: { + attributes: [string]: #Attribute + labels: #Labels + } + for class, gen in generated { + attributes: gen.attributes + labels: gen.labels + } +} diff --git a/tools/kicad/kicad_rs/src/lib.rs b/tools/kicad/kicad_rs/src/lib.rs index 66dc27e..aae37a4 100644 --- a/tools/kicad/kicad_rs/src/lib.rs +++ b/tools/kicad/kicad_rs/src/lib.rs @@ -1,4 +1,4 @@ -pub mod classifier; +pub mod policy; pub mod codec; pub mod error; pub mod eval; diff --git a/tools/kicad/kicad_rs/src/policy.rs b/tools/kicad/kicad_rs/src/policy.rs new file mode 100644 index 0000000..4f02755 --- /dev/null +++ b/tools/kicad/kicad_rs/src/policy.rs @@ -0,0 +1,197 @@ +use crate::codec; +use crate::error::{errorf, DynamicResult}; +use crate::labels::LabelsMatch; +use crate::requirements::Requirement; +use crate::types::{Component, Schematic}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::hash::Hash; +use std::io::Write; +use std::iter::FromIterator; +use std::path::Path; +use std::process; +use std::process::{Command, Stdio}; +use std::str; +use tempfile::tempdir; + +const CUE_COMMON_BYTES: &'static str = include_str!("cue/common.cue"); +const CUE_MAP_BYTES: &'static str = include_str!("cue/map.cue"); +const CUE_MAP_FILE: &'static str = "map.cue"; +const CUE_REDUCE_BYTES: &'static str = include_str!("cue/reduce.cue"); +const CUE_REDUCE_FILE: &'static str = "reduce.cue"; +const CUE_POLICY_SCHEMA_BYTES: &'static str = include_str!("cue/policy_schema.cue"); +const CUE_POLICY_SCHEMA_FILE: &'static str = "policy_schema.cue"; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct SchematicHolder { + pub schematic: Schematic, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct ComponentClassifier { + // The class that shall be applied to a component matching these requirements + pub class: String, + // Label matching + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] + pub labels: HashMap, + // Attribute matching + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(default)] + pub attributes: HashMap, +} + +pub fn apply(cue_policy_file: &Path, sch: Schematic) -> DynamicResult { + // Write the in-binary policy schema file to a temporary directory + let tmp_dir = tempdir()?; + let mut m = HashMap::new(); + m.insert( + CUE_POLICY_SCHEMA_FILE.into(), + Vec::from([CUE_COMMON_BYTES, CUE_POLICY_SCHEMA_BYTES]), + ); + let m = write_temp_files(&tmp_dir, m)?; + + // Execute the given policy file using CUE and decode the resulting YAML ComponentClassifier list + // This will use the first, classification part of the given CUE file + let c = Command::new("cue") + .arg("export") + .arg(&m.get(CUE_POLICY_SCHEMA_FILE).unwrap()) + .arg(cue_policy_file) + .arg("--expression=#Classifiers") + .arg("--out=yaml") + .output()?; + + // Decode the classifiers from the YAML output + let classifiers: Vec = codec::unmarshal_yaml(c.stdout.as_slice())?; + + // Make the now-owned schematic mutable for passing into the classifier function + let mut sch = sch; + classify_components(&mut sch, &classifiers); + + // Marshal the now-classified Schematic back to YAML, inside the SchematicHolder struct + // (to support arbitrary Schematic nesting) for piping to CUE defaulting and validation step + let mut schematic_yaml: Vec = Vec::new(); + let sch_holder = SchematicHolder{ + schematic: sch, + }; + codec::marshal_yaml(&sch_holder, &mut schematic_yaml)?; + + // Write the in-binary "map and reduction" CUE files to a temporary directory + let tmp_dir = tempdir()?; + let mut m = HashMap::new(); + m.insert( + CUE_MAP_FILE.into(), + Vec::from([CUE_COMMON_BYTES, CUE_MAP_BYTES, CUE_POLICY_SCHEMA_BYTES]), + ); + m.insert( + CUE_REDUCE_FILE.into(), + Vec::from([CUE_COMMON_BYTES, CUE_REDUCE_BYTES]), + ); + let m = write_temp_files(&tmp_dir, m)?; + + // Assemble the CUE command that will apply the policy of the given cue_policy_file + let cmd = format!( + "cue export --out=yaml {} {} yaml: - | cue export --out=yaml {} yaml: -", + cue_policy_file + .as_os_str() + .to_str() + .ok_or("couldn't get policy file path")?, + m.get(CUE_MAP_FILE).unwrap(), + m.get(CUE_REDUCE_FILE).unwrap(), + ); + + // Execute the command with schematic_yaml passed to stdin, and capture stdout/stderr. + let output = exec_shell_pipe(&cmd, schematic_yaml)?; + // If there's data in stderr, we got an error we shall pass through + if !output.stderr.is_empty() { + writeln!(std::io::stderr(), "{}", str::from_utf8(&output.stderr)?)?; + return Err(errorf("policy error occurred")); + } + + // If we were successful in passing it through, unmarshal back into the Schematic + let sch_holder: SchematicHolder = codec::unmarshal_yaml(output.stdout.as_slice())?; + + Ok(sch_holder.schematic) +} + +// classify_components recursively walks through a Schematic, and assigns the Component.classes field +fn classify_components(sch: &mut Schematic, classifiers: &Vec) { + for (_, comp) in sch.components.iter_mut() { + comp.classes = classify_component(comp, classifiers); + } + for (_, sch) in sch.sub_schematics.iter_mut() { + classify_components(sch, classifiers); + } +} + +// classify_component returns a list of classes for a given component, given the set of classifiers +fn classify_component(comp: &Component, classifiers: &Vec) -> Vec { + // Map all classifiers to their name if the component matches the classifier + let matched_classes: Vec = classifiers + .iter() + .filter_map(|classifier| { + // Require that both all labels and attribute requirements match + if !classifier.labels.matches(&comp.labels.to_map()) { + return None; + } + if !classifier.attributes.matches(&comp.attributes) { + return None; + } + + // If we get all the way here, we have "matched" with this class. + return Some(classifier.class.clone()); + }) + .collect(); + + // As there might be many classifiers of the same name that have matched with a component, + // filter all duplicates + filter_duplicates(&matched_classes) +} + +// filter_duplicates inserts all items into a HashSet, and builds a new vector without any duplicates +fn filter_duplicates(list: &Vec) -> Vec { + let f: HashSet<&T> = HashSet::from_iter(list.iter()); + f.iter().map(|s| s.clone().to_owned()).collect() +} + +fn write_temp_files( + tmp_dir: &tempfile::TempDir, + m: HashMap>, +) -> DynamicResult> { + m.into_iter() + .map(|a| { + let p = tmp_dir.path().join(&a.0); + let p = p.to_str().ok_or("couldn't build path")?; + let mut f = File::create(p)?; + for bytes in a.1 { + writeln!(f, "{}", bytes)?; + } + f.flush()?; + Ok((a.0.clone(), p.into())) + }) + .collect() +} + +fn exec_shell_pipe(cmd: &str, stdin_data: Vec) -> DynamicResult { + let mut child = Command::new("/bin/sh") + .arg("-c") + .arg(cmd) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let mut stdin = child.stdin.take().expect("Failed to open stdin"); + std::thread::spawn(move || { + stdin + .write_all(&stdin_data) + .expect("Failed to write to stdin"); + }); + + Ok(child.wait_with_output()?) +} From f5ac6de2c8f96f9bcc97e2e96cc3eab0607f3a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 15 Jul 2021 20:24:42 +0300 Subject: [PATCH 4/5] Customize the sample files to demonstrate the functionality --- hat-psu/hat-psu/BD9E302EFJ-5V1.sch | 118 +++++++++++++++++++---------- hat-psu/hat-psu/BMC.sch | 8 +- 2 files changed, 83 insertions(+), 43 deletions(-) diff --git a/hat-psu/hat-psu/BD9E302EFJ-5V1.sch b/hat-psu/hat-psu/BD9E302EFJ-5V1.sch index b7bd682..ee410d5 100644 --- a/hat-psu/hat-psu/BD9E302EFJ-5V1.sch +++ b/hat-psu/hat-psu/BD9E302EFJ-5V1.sch @@ -3,7 +3,7 @@ EELAYER 30 0 EELAYER END $Descr A4 11693 8268 encoding utf-8 -Sheet 5 5 +Sheet 2 2 Title "Compute unit HAT attachment" Date "2021-06-13" Rev "0.1.1" @@ -14,28 +14,33 @@ Comment3 "https://github.com/racklet/racklet" Comment4 "Author: Verneri Hirvonen" $EndDescr $Comp -L Device:L L? +L Device:L L2 U 1 1 60A118D6 P 6700 3600 -AR Path="/60A118D6" Ref="L?" Part="1" +AR Path="/60A118D6" Ref="L2" Part="1" AR Path="/60A0E5A4/60A118D6" Ref="L2" Part="1" -F 0 "L2" V 6890 3600 50 0000 C CNN -F 1 "8.2u" V 6799 3600 50 0000 C CNN +AR Path="/60F06CF1/60A118D6" Ref="L?" Part="1" +F 0 "L?" V 6890 3600 50 0000 C CNN +F 1 "8.2" V 6799 3600 50 0000 C CNN F 2 "racklet:DEM8045C" H 6700 3600 50 0001 C CNN F 3 "https://media.digikey.com/pdf/Data%20Sheets/Murata%20PDFs/DEMO80(30,40,45)C%20Type.pdf" H 6700 3600 50 0001 C CNN +F 4 "true" V 6700 3600 50 0001 C CNN "ICCC_Show" +F 5 "8.2" V 6700 3600 50 0001 C CNN "value_expr" 1 6700 3600 0 -1 -1 0 $EndComp Text Notes 4600 4650 0 50 ~ 0 Should AGND\nhave a separate\nground plane? $Comp -L Device:C_Small C6 +L Device:C_Small C? U 1 1 60A1617F P 7150 3750 -F 0 "C6" H 7242 3796 50 0000 L CNN -F 1 "47u" H 7242 3705 50 0000 L CNN +F 0 "C?" H 7242 3796 50 0000 L CNN +F 1 "47" H 7242 3705 50 0000 L CNN F 2 "Capacitor_SMD:C_1206_3216Metric" H 7150 3750 50 0001 C CNN F 3 "~" H 7150 3750 50 0001 C CNN +F 4 "true" H 7150 3750 50 0001 C CNN "ICCC_Show" +F 5 "47" H 7150 3750 50 0001 C CNN "value_expr" 1 7150 3750 1 0 0 -1 $EndComp @@ -47,10 +52,10 @@ Wire Wire Line 7150 3600 7700 3600 Connection ~ 7150 3600 $Comp -L power:GND #PWR010 +L power:GND #PWR? U 1 1 60A17059 P 7700 4550 -F 0 "#PWR010" H 7700 4300 50 0001 C CNN +F 0 "#PWR?" H 7700 4300 50 0001 C CNN F 1 "GND" H 7705 4377 50 0000 C CNN F 2 "" H 7700 4550 50 0001 C CNN F 3 "" H 7700 4550 50 0001 C CNN @@ -67,13 +72,15 @@ Connection ~ 7700 3600 Wire Wire Line 7700 3600 8050 3600 $Comp -L Device:C_Small C7 +L Device:C_Small C? U 1 1 60A1779A P 8050 4100 -F 0 "C7" H 8142 4146 50 0000 L CNN -F 1 "15p" H 8142 4055 50 0000 L CNN +F 0 "C?" H 8142 4146 50 0000 L CNN +F 1 "15" H 8142 4055 50 0000 L CNN F 2 "Capacitor_SMD:C_0603_1608Metric" H 8050 4100 50 0001 C CNN F 3 "~" H 8050 4100 50 0001 C CNN +F 4 "true" H 8050 4100 50 0001 C CNN "ICCC_Show" +F 5 "15" H 8050 4100 50 0001 C CNN "value_expr" 1 8050 4100 1 0 0 -1 $EndComp @@ -92,10 +99,10 @@ Connection ~ 7700 4250 Wire Wire Line 7700 4250 7700 4200 $Comp -L power:GND #PWR09 +L power:GND #PWR? U 1 1 60A1950D P 7150 3900 -F 0 "#PWR09" H 7150 3650 50 0001 C CNN +F 0 "#PWR?" H 7150 3650 50 0001 C CNN F 1 "GND" H 7155 3727 50 0000 C CNN F 2 "" H 7150 3900 50 0001 C CNN F 3 "" H 7150 3900 50 0001 C CNN @@ -105,13 +112,15 @@ $EndComp Wire Wire Line 7150 3900 7150 3850 $Comp -L Device:C_Small C5 +L Device:C_Small C? U 1 1 60A1C127 P 6300 4050 -F 0 "C5" H 6392 4096 50 0000 L CNN -F 1 "5600p" H 6392 4005 50 0000 L CNN +F 0 "C?" H 6392 4096 50 0000 L CNN +F 1 "5600" H 6392 4005 50 0000 L CNN F 2 "Capacitor_SMD:C_0603_1608Metric" H 6300 4050 50 0001 C CNN F 3 "~" H 6300 4050 50 0001 C CNN +F 4 "true" H 6300 4050 50 0001 C CNN "ICCC_Show" +F 5 "5600" H 6300 4050 50 0001 C CNN "value_expr" 1 6300 4050 1 0 0 -1 $EndComp @@ -120,9 +129,13 @@ L Device:R_Small R6 U 1 1 60A1CEC4 P 6300 4300 F 0 "R6" H 6241 4254 50 0000 R CNN -F 1 "16.2k" H 6241 4345 50 0000 R CNN +F 1 "8.1" H 6241 4345 50 0000 R CNN F 2 "Resistor_SMD:R_0603_1608Metric" H 6300 4300 50 0001 C CNN F 3 "~" H 6300 4300 50 0001 C CNN +F 4 "true" H 6300 4300 50 0001 C CNN "ICCC_Show" +F 5 "16.2/2" H 6300 4300 50 0001 C CNN "value_expr" +F 6 "R7.Value/500.0" H 6300 4300 50 0001 C CNN "tolerance_expr" +F 7 "1.098" H 6300 4300 50 0001 C CNN "tolerance" 1 6300 4300 -1 0 0 1 $EndComp @@ -131,20 +144,24 @@ L Device:R_Small R7 U 1 1 60A1D4C6 P 7700 4100 F 0 "R7" H 7641 4054 50 0000 R CNN -F 1 "549k" H 7641 4145 50 0000 R CNN +F 1 "549" H 7641 4145 50 0000 R CNN F 2 "Resistor_SMD:R_0603_1608Metric" H 7700 4100 50 0001 C CNN F 3 "~" H 7700 4100 50 0001 C CNN +F 4 "true" H 7700 4100 50 0001 C CNN "ICCC_Show" +F 5 "549" H 7700 4100 50 0001 C CNN "value_expr" 1 7700 4100 -1 0 0 1 $EndComp $Comp -L Device:R_Small R8 +L Device:R_Small R? U 1 1 60A1DFD2 P 7700 4400 -F 0 "R8" H 7641 4354 50 0000 R CNN -F 1 "102k" H 7641 4445 50 0000 R CNN +F 0 "R?" H 7641 4354 50 0000 R CNN +F 1 "102" H 7641 4445 50 0000 R CNN F 2 "Resistor_SMD:R_0603_1608Metric" H 7700 4400 50 0001 C CNN F 3 "~" H 7700 4400 50 0001 C CNN +F 4 "true" H 7700 4400 50 0001 C CNN "ICCC_Show" +F 5 "102" H 7700 4400 50 0001 C CNN "value_expr" 1 7700 4400 -1 0 0 1 $EndComp @@ -157,10 +174,10 @@ Wire Wire Line Wire Wire Line 6300 4450 6300 4400 $Comp -L power:GND #PWR07 +L power:GND #PWR? U 1 1 60A21A70 P 6300 4450 -F 0 "#PWR07" H 6300 4200 50 0001 C CNN +F 0 "#PWR?" H 6300 4200 50 0001 C CNN F 1 "GND" H 6305 4277 50 0000 C CNN F 2 "" H 6300 4450 50 0001 C CNN F 3 "" H 6300 4450 50 0001 C CNN @@ -170,21 +187,27 @@ $EndComp Wire Wire Line 7700 4250 7550 4250 $Comp -L Device:C_Small C3 +L Device:C_Small C? U 1 1 60A2522A P 3900 4000 -F 0 "C3" H 3992 4046 50 0000 L CNN -F 1 "0.1u" H 3992 3955 50 0000 L CNN +F 0 "C?" H 3992 4046 50 0000 L CNN +F 1 "0.1" H 3992 3955 50 0000 L CNN F 2 "Capacitor_SMD:C_0603_1608Metric" H 3900 4000 50 0001 C CNN F 3 "~" H 3900 4000 50 0001 C CNN +F 4 "true" H 3900 4000 50 0001 C CNN "ICCC_Show" +F 5 "0.1" H 3900 4000 50 0001 C CNN "value_expr" +F 6 "35" H 3900 4000 50 0001 C CNN "voltagerating_expr" +F 7 "V" H 3900 4000 50 0001 C CNN "voltagerating_unit" +F 8 "35 V" H 3900 4000 50 0001 C CNN "voltagerating" +F 9 "Must be at least as large as the input voltage" H 3900 4000 50 0001 C CNN "voltagerating_comment" 1 3900 4000 1 0 0 -1 $EndComp $Comp -L power:GND #PWR02 +L power:GND #PWR? U 1 1 60A25F03 P 3700 4300 -F 0 "#PWR02" H 3700 4050 50 0001 C CNN +F 0 "#PWR?" H 3700 4050 50 0001 C CNN F 1 "GND" H 3705 4127 50 0000 C CNN F 2 "" H 3700 4300 50 0001 C CNN F 3 "" H 3700 4300 50 0001 C CNN @@ -201,10 +224,10 @@ Wire Wire Line Wire Wire Line 3500 4250 3700 4250 $Comp -L power:GND #PWR06 +L power:GND #PWR? U 1 1 60A1408D P 4650 3950 -F 0 "#PWR06" H 4650 3700 50 0001 C CNN +F 0 "#PWR?" H 4650 3700 50 0001 C CNN F 1 "GND" H 4655 3777 50 0000 C CNN F 2 "" H 4650 3950 50 0001 C CNN F 3 "" H 4650 3950 50 0001 C CNN @@ -222,13 +245,15 @@ VOUT Text HLabel 3300 3700 0 50 Input ~ 0 VIN $Comp -L Device:C_Small C4 +L Device:C_Small C? U 1 1 60A2ECA9 P 5350 3050 -F 0 "C4" V 5121 3050 50 0000 C CNN -F 1 "0.1u" V 5212 3050 50 0000 C CNN +F 0 "C?" V 5121 3050 50 0000 C CNN +F 1 "0.1" V 5212 3050 50 0000 C CNN F 2 "Capacitor_SMD:C_0603_1608Metric" H 5350 3050 50 0001 C CNN F 3 "~" H 5350 3050 50 0001 C CNN +F 4 "true" V 5350 3050 50 0001 C CNN "ICCC_Show" +F 5 "0.1" V 5350 3050 50 0001 C CNN "value_expr" 1 5350 3050 0 1 1 0 $EndComp @@ -263,21 +288,24 @@ Wire Wire Line Wire Wire Line 3500 3900 3500 3700 $Comp -L Device:C_Small C2 +L Device:C_Small C? U 1 1 60A24C15 P 3500 4000 -F 0 "C2" H 3592 4046 50 0000 L CNN -F 1 "10u" H 3592 3955 50 0000 L CNN +F 0 "C?" H 3592 4046 50 0000 L CNN +F 1 "10 F" H 3592 3955 50 0000 L CNN F 2 "Capacitor_SMD:C_1206_3216Metric" H 3500 4000 50 0001 C CNN F 3 "~" H 3500 4000 50 0001 C CNN +F 4 "true" H 3500 4000 50 0001 C CNN "ICCC_Show" +F 5 "C3.Value*100" H 3500 4000 50 0001 C CNN "value_expr" +F 6 "F" H 3500 4000 50 0001 C CNN "value_unit" 1 3500 4000 1 0 0 -1 $EndComp $Comp -L power:GND #PWR08 +L power:GND #PWR? U 1 1 60A7FBCB P 6450 3700 -F 0 "#PWR08" H 6450 3450 50 0001 C CNN +F 0 "#PWR?" H 6450 3450 50 0001 C CNN F 1 "GND" H 6455 3527 50 0000 C CNN F 2 "" H 6450 3700 50 0001 C CNN F 3 "" H 6450 3700 50 0001 C CNN @@ -294,13 +322,19 @@ Connection ~ 5900 3700 Wire Wire Line 5900 3700 5800 3700 $Comp -L racklet:BD9E302EFJ-E2 U2 +L racklet:BD9E302EFJ-E2 U? U 1 1 60C766A3 P 5300 3750 -F 0 "U2" H 5275 4265 50 0000 C CNN +F 0 "U?" H 5275 4265 50 0000 C CNN F 1 "BD9E302EFJ-E2" H 5275 4174 50 0000 C CNN F 2 "Package_SO:HTSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.4x3.2mm_ThermalVias" H 5200 4250 50 0001 C CNN F 3 "https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/switching_regulator/bd9e302efj-e.pdf" H 5200 3750 50 0001 C CNN +F 4 "true" H 5300 3750 50 0001 C CNN "ICCC_Show" +F 5 "BD9E302EFJ-E2" H 5300 3750 50 0001 C CNN "Model" +F 6 "3" H 5300 3750 50 0001 C CNN "currentoutput_min_expr" +F 7 "3" H 5300 3750 50 0001 C CNN "currentOutput_min" +F 8 "12" H 5300 3750 50 0001 C CNN "min_vin_max_expr" +F 9 "24" H 5300 3750 50 0001 C CNN "max_vin_min_expr" 1 5300 3750 1 0 0 -1 $EndComp diff --git a/hat-psu/hat-psu/BMC.sch b/hat-psu/hat-psu/BMC.sch index fede590..937bb5c 100644 --- a/hat-psu/hat-psu/BMC.sch +++ b/hat-psu/hat-psu/BMC.sch @@ -3,7 +3,7 @@ EELAYER 30 0 EELAYER END $Descr A4 11693 8268 encoding utf-8 -Sheet 4 5 +Sheet 1 2 Title "Compute unit HAT attachment" Date "2021-06-13" Rev "0.1.1" @@ -167,4 +167,10 @@ Wire Wire Line 4000 5450 3250 5450 Text HLabel 3250 5450 0 50 Input ~ 0 3V3 +$Sheet +S 7700 3750 950 350 +U 60F06CF1 +F0 "BD9E302EFJ" 50 +F1 "BD9E302EFJ-5V1.sch" 50 +$EndSheet $EndSCHEMATC From 7ed477536201bf794cac20517f18a20e36bc48c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 15 Jul 2021 20:24:52 +0300 Subject: [PATCH 5/5] Document how to use the three components --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index 4dab223..3c48f03 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,53 @@ This repository holds the designs for various hardware prototypes. +## kicad_rs sample usage + +First, `cd tools/kicad/kicad_rs` when running from source. + +### Evaluator + +Reads from Stdin: No +Writes to Stdout: No +Command line arguments: + +1. Schematic file to evaluate, will update in-place + +```bash +# This command will update the file in place +cargo run --bin=evaluator ../../../hat-psu/hat-psu/BD9E302EFJ-5V1.sch +``` + +### Parser + +Reads from Stdin: No +Writes to Stdout: Yes +Command line arguments: + +1. Schematic file to parse + +```bash +# Or any other .sch file. This command writes to stdout, you can save it in a +# file like this or pipe it to the classifier. +cargo run --bin=parser ../../../hat-psu/hat-psu/BD9E302EFJ-5V1.sch > parsed.yaml +``` + +### Classifier + +Reads from Stdin: Yes +Writes to Stdout: Yes +Command line arguments: + +1. Policy file written in CUE to use. A sample file is given in `tools/kicad/kicad_rs/sample_policy.cue` + +```bash +# Read from the parsed file like this and save to a file, or... +cat parsed.yaml | cargo run --bin=classifier sample_policy.cue > classified.yaml + +# ... pipe the output from the parser like this (writes to stdout) +cargo run --bin=parser ../../../hat-psu/hat-psu/BD9E302EFJ-5V1.sch | cargo run --bin=classifier sample_policy.cue +``` + ## Contributing Please see [CONTRIBUTING.md](CONTRIBUTING.md) and our [Code Of Conduct](CODE_OF_CONDUCT.md).