Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/commands/implement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Implement any code changes described in the documentation (\*.md) changes (git
diff) and `plans/` directory.

Note for this command, the documentation is driving the code changes, not the
other way around.
7 changes: 7 additions & 0 deletions .claude/commands/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Create a plan file named `plans/<current date>-"$ARGUMENTS".md`. Replace spaces
with '-' in the filename.

Then switch to plan mode and create a plan for coding the current changes
described in the README.md and other documentation files. Note, in this phase,
we are using documentation to drive the code, not the other way around. When the
plan is complete, write it to the plan file.
4 changes: 4 additions & 0 deletions .claude/commands/update-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Update any relevant documentation for the current code changes (git diff)

The changelog should describe changes as benefits to the user. It should not
include technical details of the change, that is what git log is for.
6 changes: 6 additions & 0 deletions .harper-dictionary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
GitPLM
IPN
MCUs
MPN
PLM
partmaster
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For more details or to discuss releases, please visit the

## [[0.7.1] - 2025-07-11](https://github.com/git-plm/gitplm/releases/tag/v0.7.1)

- rename release to more friendly names
- Rename release to more friendly names

## [[0.7.0] - 2025-07-11](https://github.com/git-plm/gitplm/releases/tag/v0.7.0)

Expand Down Expand Up @@ -45,8 +45,8 @@ For more details or to discuss releases, please visit the

## [[0.6.1] - 2025-06-26](https://github.com/git-plm/gitplm/releases/tag/v0.6.1)

- add `-pmDir` command line parameter to specify parts database directory
- add support for loading multiple partmaster CSV files from a directory
- Add `-pmDir` command line parameter to specify parts database directory
- Add support for loading multiple partmaster CSV files from a directory
- **breaking changes**
- changed CSV column heading "qnty" to "qty"
- breaking change: switched to using ',' in CSV files for delimiter instead of
Expand Down
180 changes: 135 additions & 45 deletions README.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import (
"gopkg.in/yaml.v2"
)

type HTTPConfig struct {
Enabled bool `yaml:"enabled"`
Port int `yaml:"port"`
Token string `yaml:"token"`
}

type Config struct {
PMDir string `yaml:"pmDir"`
PMDir string `yaml:"pmDir"`
HTTP HTTPConfig `yaml:"http"`
}

func loadConfig() (*Config, error) {
Expand Down
15 changes: 15 additions & 0 deletions gitplm.kicad_httplib
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"meta": {
"version": 1.0
},
"name": "GitPLM KiCad HTTP Library",
"description": "Verifiable parts database and KiCad Libraries",
"source": {
"type": "REST_API",
"api_version": "v1",
"root_url": "http://localhost:7654/",
"token": "",
"timeout_parts_seconds": 15,
"timeout_categories_seconds": 15
}
}
77 changes: 58 additions & 19 deletions kicad_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,9 @@ func (s *KiCadServer) authenticate(r *http.Request) bool {
func (s *KiCadServer) getCategories() []KiCadCategory {
categoryMap := make(map[string]bool)

// Extract categories from CSV files and IPNs
// Extract categories from CSV files - use IPNs from each file
for _, file := range s.csvCollection.Files {
// Try to extract category from filename (e.g., cap.csv -> CAP)
if fileName := strings.TrimSuffix(strings.ToUpper(file.Name), ".CSV"); fileName != "" && len(fileName) == 3 {
categoryMap[fileName] = true
}

// Also extract from IPNs if they exist
// Extract from IPNs if they exist
if ipnIdx := s.findColumnIndex(file, "IPN"); ipnIdx >= 0 {
for _, row := range file.Rows {
if len(row) > ipnIdx && row[ipnIdx] != "" {
Expand Down Expand Up @@ -149,8 +144,8 @@ func (s *KiCadServer) findColumnIndex(file *CSVFile, columnName string) int {

// extractCategory extracts the CCC component from an IPN
func (s *KiCadServer) extractCategory(ipnStr string) string {
// IPN format: CCC-NNN-VVVV
re := regexp.MustCompile(`^([A-Z][A-Z][A-Z])-(\d\d\d)-(\d\d\d\d)$`)
// IPN format: CCC-NNNN-VVVV (also supports CCC-NNN-VVVV for legacy)
re := regexp.MustCompile(`^([A-Z][A-Z][A-Z])-(\d{3,4})-(\d{4})$`)
matches := re.FindStringSubmatch(ipnStr)
if len(matches) >= 2 {
return matches[1]
Expand All @@ -161,9 +156,24 @@ func (s *KiCadServer) extractCategory(ipnStr string) string {
// getCategoryDisplayName returns a human-readable name for a category
func (s *KiCadServer) getCategoryDisplayName(category string) string {
displayNames := map[string]string{
"ANA": "Analog ICs",
"ART": "Artwork",
"CAP": "Capacitors",
"RES": "Resistors",
"CON": "Connectors",
"CPD": "Compound Components",
"DIO": "Diodes",
"ICS": "Integrated Circuits",
"IND": "Inductors",
"MCU": "Microcontrollers",
"MPU": "Microprocessors",
"OPT": "Optical Components",
"OSC": "Oscillators",
"PWR": "Power Components",
"REG": "Regulators",
"RES": "Resistors",
"RFM": "RF Modules",
"SWI": "Switches",
"XTR": "Transceivers",
"LED": "LEDs",
"SCR": "Screws",
"MCH": "Mechanical",
Expand All @@ -177,9 +187,7 @@ func (s *KiCadServer) getCategoryDisplayName(category string) string {
"FIX": "Fixtures",
"CNT": "Connectors",
"IC": "Integrated Circuits",
"OSC": "Oscillators",
"XTL": "Crystals",
"IND": "Inductors",
"FER": "Ferrites",
"FUS": "Fuses",
"SW": "Switches",
Expand All @@ -202,9 +210,24 @@ func (s *KiCadServer) getCategoryDisplayName(category string) string {
// getCategoryDescription returns a description for a category
func (s *KiCadServer) getCategoryDescription(category string) string {
descriptions := map[string]string{
"ANA": "Analog integrated circuits",
"ART": "Artwork and graphics components",
"CAP": "Capacitor components",
"RES": "Resistor components",
"CON": "Connector components",
"CPD": "Compound and complex components",
"DIO": "Diode components",
"ICS": "Integrated circuit components",
"IND": "Inductor components",
"MCU": "Microcontroller components",
"MPU": "Microprocessor components",
"OPT": "Optical components",
"OSC": "Oscillator components",
"PWR": "Power supply and management components",
"REG": "Voltage regulator components",
"RES": "Resistor components",
"RFM": "RF module components",
"SWI": "Switch components",
"XTR": "Transceiver components",
"LED": "Light emitting diode components",
"SCR": "Screw and fastener components",
"MCH": "Mechanical components",
Expand All @@ -218,9 +241,7 @@ func (s *KiCadServer) getCategoryDescription(category string) string {
"FIX": "Fixture components",
"CNT": "Connector components",
"IC": "Integrated circuit components",
"OSC": "Oscillator components",
"XTL": "Crystal components",
"IND": "Inductor components",
"FER": "Ferrite components",
"FUS": "Fuse components",
"SW": "Switch components",
Expand Down Expand Up @@ -319,24 +340,42 @@ func (s *KiCadServer) getPartDetail(partID string) *KiCadPartDetail {
if rowPartID == partID {
fields := make(map[string]KiCadPartField)
partName := ""
symbolID := ""
category := s.extractCategory(partID)

// Add all fields from the CSV dynamically
for i, header := range file.Headers {
if i < len(row) && row[i] != "" && header != "" {
fields[header] = KiCadPartField{Value: row[i]}

// Set name from Description field
if header == "Description" {
partName = row[i]
}

// Set symbol from Symbol field
if header == "Symbol" {
symbolID = row[i]
} else {
// Add field to fields map (exclude Symbol as it goes in symbolIdStr)
fields[header] = KiCadPartField{Value: row[i]}
}
}
}

// Error if no Symbol field found
if symbolID == "" {
log.Printf("ERROR: Part %s has no Symbol field defined", partID)
}

// Format ID as category/part-id (e.g., "rfm/RFM-0000-0001")
formattedID := partID
if category != "" {
formattedID = strings.ToLower(category) + "/" + partID
}

return &KiCadPartDetail{
ID: partID,
ID: formattedID,
Name: partName,
SymbolIDStr: s.getSymbolIDFromCategory(category),
SymbolIDStr: symbolID,
ExcludeFromBOM: "false", // Default to include in BOM
Fields: fields,
}
Expand Down
13 changes: 10 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,22 @@ func main() {
os.Exit(-1)
}

// Set defaults from config
defaultPort := 7654
if config.HTTP.Port > 0 {
defaultPort = config.HTTP.Port
}
defaultToken := config.HTTP.Token

flagRelease := flag.String("release", "", "Process release for IPN (ex: PCB-056-0005, ASY-002-0023)")
flagVersion := flag.Bool("version", false, "display version of this application")
flagSimplify := flag.String("simplify", "", "simplify a BOM file, combine lines with common MPN")
flagOutput := flag.String("out", "", "output file")
flagCombine := flag.String("combine", "", "adds BOM to output bom")
flagPMDir := flag.String("pmDir", config.PMDir, "specify location of partmaster CSV files")
flagHTTPServer := flag.Bool("http", false, "start KiCad HTTP Library API server")
flagHTTPPort := flag.Int("port", 8080, "HTTP server port")
flagHTTPToken := flag.String("token", "", "authentication token for HTTP API")
flagHTTPServer := flag.Bool("http", config.HTTP.Enabled, "start KiCad HTTP Library API server")
flagHTTPPort := flag.Int("port", defaultPort, "HTTP server port (default: 7654)")
flagHTTPToken := flag.String("token", defaultToken, "authentication token for HTTP API")
flag.Parse()

if *flagVersion {
Expand Down
Loading
Loading