diff --git a/README.md b/README.md index a0a101c..b1ccb5e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Generate a BOM output file for an Altium project on AllSpice Hub using [AllSpice Add the following steps to your actions: ```yaml -# Checkout is only needed if columns.json is committed in your Altium project repo. +# Checkout is only needed if columns.yml is committed in your Altium project repo. - name: Checkout uses: actions/checkout@v3 @@ -16,89 +16,109 @@ Add the following steps to your actions: with: # The path to the project file in your repo (.PrjPcb for Altium, .DSN for OrCad). source_path: Archimajor.PrjPcb - # [optional] A path to a JSON file mapping columns to the component attributes - # they are from. This file must be provided. - # Default: 'columns.json' - columns: .allspice/columns.json + # [optional] A path to a YAML file mapping columns to the component + # attributes they are from. + # Default: 'columns.yml' + columns: .allspice/columns.yml # [optional] The path to the output file that will be generated. # Default: 'bom.csv' output_file_name: bom.csv # [optional] A comma-separated list of columns to group the BOM by. If empty # or not present, the BOM will be flat. # Default: '' - group_by: 'Part ID' + group_by: "Part ID" # [optional] The variant of the project to generate the BOM for. If empty # or not present, the BOM will be generated for the default variant. # Default: '' - variant: '' + variant: "" ``` ### Customizing the Attributes Extracted by the BOM Script -This script relies on a `columns.json` file. This file maps the Component -Attributes in the SchDoc files (right) to the columns of the BOM (left). +This script relies on a YAML file to specify the columns in the BOM and which +attributes or properties of the components they are populated from. This file is +typically called `columns.yml` and can be checked into your repo. To learn more +about YAML, [check out the AllSpice Knowledge Base.](https://learn.allspice.io/docs/yaml) -An example for Altium `columns.json` file content is: +The format of this YAML file is as follows: -```json -{ - "Part Number": ["PART", "MANUFACTURER #", "_part_id"], - "Manufacturer": ["Manufacturer", "MANUFACTURER"], - "Designator": ["Designator"], - "Description": ["PART DESCRIPTION"] -} +```yml +columns: + - name: Manufacturer + part_attributes: + - Manufacturer + - MANUFACTURER + - name: Part Number + part_attributes: + - PART + - MANUFACTURER \# + - _part_id + - name: Designator + part_attributes: Designator + - name: Description + part_attributes: + - PART DESCRIPTION + - _description ``` -In this file, the keys are the names of the columns in the BOM, and the values -are a list of the names of the attributes in the SchDoc files that should be -mapped to that column. For example, if your part number is stored either in the -`PART` or `MANUFACTURER #` attribute, you would add both of those to the list. -If there is only one attribute, you can omit the list and just use a string. The -script checks these attributes in order, and uses the _first_ one it finds. So -if both `PART` and `MANUFACTURER #` are defined, it will use `PART`. +First, you have the key `columns:` which is mapped to a list. Each element of +the list has two key/value pairs. The first is `name`, which is used to +as the name of the column. Next, you have `part_attributes`. This can either be +just a string (like in the case of the `Designator` column) or a list of strings +(like in the other cases.) -An example for OrCad `columns.json` file content is: +If `part_attributes` is just a string, that property or attribute of the +component is used as the value for that column. If that property is not present +in a particular part, that column will be blank for that part. If +`part_attributes` is a list, those properties will be checked in the order they +are defined for each part. The _first_ property found is used as the value for +that column in the row for that part. So if both `PART` and `MANUFACTURER #` are +defined, it will use `PART`. -```json -{ - "Part Number": ["Part Number", "_name"], - "Designator": ["Part Reference"], - "Type": ["Part Type"], - "Value": ["Value"] -} +An example for OrCad `columns.yml` file content is: + +```yml +columns: + - name: Part Number + part_attributes: + - Part Number + - _name + - name: Designator + part_attributes: Part Reference + - name: Type + part_attributes: Part Type + - name: Value + part_attributes: Value ``` -Note that py-allspice also adds static attributes: `_part_id`, `_description`, and `_name`. -These correspond to the Library Reference and description fields of the -component. The underscore is added ahead of the name to prevent these additional -attributes from overriding any of your own. You can use these like: - -```json -{ - "Description": ["PART DESCRIPTION", "_description"], - "Part Number": ["PART", "_part_id"] -} -``` - -Where the BOM generation will use the attribute "PART DESCRIPTION" if it exists -for a given component, and "_description" otherwise. Same for "PART" and "_part_id". - -By default, the script picks up a `columns.json` file from the working +By default, the action will pick up a `columns.yml` file from the working directory. If you want to keep it in a different place, or rename it, you can -pass the `--columns` argument to the script to specify where it is. +pass the `--columns` argument to the step in the workflow to specify where it +is. + +### Py-allspice injected attributes + +Note that py-allspice also adds a few static attributes, which are taken from +the part itself, and not from the properties or attributes. For Altium projects, +`_part_id` and `_description` are available, which correspond to the Library +Reference and Description fields of the component. For OrCAD projects, `_name` +is available, which corresponds to the name of the component. + +The underscore is added ahead of the name to prevent these additional attributes +from overriding any of your own. ## Group By You can also group lines by a column value. The most common is `_part_id`. You -can combine this with the columns json example above, like so: +can combine this with the columns YAML example above, like so: ```yaml - name: Generate BOM uses: https://hub.allspice.io/Actions/generate-bom@v0.3 with: project_path: Archimajor.PrjPcb - columns: .allspice/columns.json - group_by: 'Part ID' + columns: .allspice/columns.yml + group_by: "Part ID" ``` Which will generate a BOM squashed by components with matchin Part IDs. @@ -113,9 +133,9 @@ to the script. For example: uses: https://hub.allspice.io/Actions/generate-bom@v0.3 with: project_path: Archimajor.PrjPcb - columns: .allspice/columns.json + columns: .allspice/columns.yml output_file_name: bom-lite.csv - variant: 'LITE' + variant: "LITE" ``` When no variant is given, the BOM is generated without considering any variants. diff --git a/entrypoint.py b/entrypoint.py index 8acac25..8b3ed25 100755 --- a/entrypoint.py +++ b/entrypoint.py @@ -5,8 +5,8 @@ import argparse import csv -import json import os +import yaml import sys from contextlib import ExitStack @@ -32,10 +32,10 @@ if __name__ == "__main__": parser.add_argument( "--columns", help=( - "A path to a JSON file mapping columns to the attributes they are from. See the README " - "for more details. Defaults to 'columns.json'." + "A path to a YAML file mapping columns to the attributes they are from. See the README " + "for more details. Defaults to 'columns.yml'." ), - default="columns.json", + default="columns.yml", ) parser.add_argument( "--source_ref", @@ -71,8 +71,17 @@ if __name__ == "__main__": args = parser.parse_args() columns_file = args.columns - with open(columns_file, "r") as f: - columns = json.loads(f.read()) + columns = {} + try: + with open(columns_file, "r") as f: + columns_data = yaml.safe_load(f.read()) + for column_value in columns_data["columns"]: + columns[column_value["name"]] = column_value["part_attributes"] + except KeyError as e: + print(f"Error: columns file {columns_file} does not seem to be in the right format.") + print("Please refer to the README for more information.") + print(f"Caused by: {e}") + sys.exit(1) auth_token = os.environ.get("ALLSPICE_AUTH_TOKEN") if auth_token is None: diff --git a/requirements.txt b/requirements.txt index 1dbdcdf..0860a6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ py-allspice==3.1.0 +pyyaml~=6.0