#!/usr/bin/env nickle # # Convert CSV parts list into vendor import format # # Copyright © 2015 Keith Packard , GPL v3+ # bool mfg_part = false; string[*] vendors; string vendor_list; string[*] not_vendors; string not_vendor_list; string input_name; string output_name; string program; file output = stdout; int argi; int lineno = 0; bool saw_error = false; bool warn_only = false; void fatal(string format, poly args ...) { File::fprintf(stderr, format, args...); exit(1); } void error(string format, poly args ...) { File::fprintf(stderr, format, args...); if (!warn_only) saw_error = true; } string[*] read_line(file f) { lineno++; string line = fgets(f); string[*] elts = String::parse_csv(line); for (int i = 0; i < dim(elts); i++) if (String::length(elts[i]) > 0 && elts[i][0] == '"') elts[i] = String::dequote(elts[i]); return elts; } string[*] header; string[*] required_elements = { "quantity", "vendor", "vendor_part_number", "mfg_part_number", "device", "value", "refdes", "loadstatus" }; bool has_header_member(string member) { for (int i = 0; i < dim(header); i++) if (header[i] == member) return true; return false; } bool has_vendor(string[*] vendors, string vendor) { for (int i = 0; i < dim(vendors); i++) if (vendors[i] == vendor) return true; return false; } void read_header(file f) { header = read_line(f); for (int i = 0; i < dim(required_elements); i++) if (!has_header_member(required_elements[i])) fatal("Missing header element \"%s\"\n", required_elements[i]); } string[string] read_entry(file f) { string[*] elements = read_line(f); string[string] entry = {}; if (dim(header) != dim(elements)) fatal("line %d: has %d instead of %d elements (%V)\n", lineno, dim(elements), dim(header), elements); for (int i = 0; i < dim(header); i++) { if (elements[i] == "") elements[i] = "unknown"; entry[header[i]] = elements[i]; } return entry; } string part_number(string[string] entry) { if (mfg_part) return entry["mfg_part_number"]; else return entry["vendor_part_number"]; } string quoted(string v) { if (String::index(v, "\"") >= 0 || String::index(v, ",") >= 0) { if (String::index(v, "\"") >= 0) { string ret = "\""; for (int i = 0; i < String::length(v); i++) { if (v[i] == '"') ret = ret + "\""; ret = ret + String::new(v[i]); } ret = ret + "\""; return ret; } else { return "\"" + v + "\""; } } else { return v; } } void process_seeed(string[string] entry) { string part_number = entry["mfg_part_number"]; if (hash_test(entry, "seeed_part_number") && entry["seeed_part_number"] != "unknown") { part_number = entry["seeed_part_number"]; } else { if (entry["loadstatus"] == "noload") { File::fprintf(stderr, "skipping part %v\n", entry); return; } } static bool start = true; if (start) { File::fprintf (output, "Part/Designator,Manufacturer Part Number/Seeed SKU, Quantity\n"); start = false; } string[*] refdes = String::wordsplit(entry["refdes"], " \t"); if (dim(refdes) > 1) File::fprintf (output, "\""); for (int i = 0; i < dim(refdes); i++) { File::fprintf (output, "%s", refdes[i]); if (i < dim(refdes) - 1) File::fprintf (output, ","); } if (dim(refdes) > 1) File::fprintf (output, "\""); File::fprintf (output, ",%s,%s\n", quoted(part_number), entry["quantity"]); } void process_goldphoenix(string[string] entry) { int units = 1000; if (entry["loadstatus"] == "noload") return; static int item = 1; static bool start = true; if (start) { File::fprintf (output, "#Item,Description,Designator,Package,Manufacturer,Manufacturer Part Number#,Supplier,Supplier Part #,QTY/BOARD,Order QTY,Unit Price, Subtotal \n"); start = false; } string[*] refdes = String::wordsplit(entry["refdes"], " \t"); File::fprintf (output, "%d,", item); /* description */ File::fprintf (output, "%s %s,", quoted(entry["device"]), quoted(entry["value"])); /* designators */ if (dim(refdes) > 1) File::fprintf (output, "\""); for (int i = 0; i < dim(refdes); i++) { File::fprintf (output, "%s", refdes[i]); if (i < dim(refdes) - 1) File::fprintf (output, ","); } if (dim(refdes) > 1) File::fprintf (output, "\""); /* rest */ File::fprintf (output, ",%s,%s,%s,%s,%s,%s,%d\n", quoted(entry["footprint"]), quoted(entry["mfg"]), quoted(entry["mfg_part_number"]), quoted(entry["vendor"]), quoted(entry["vendor_part_number"]), entry["quantity"], dim(refdes) * units); item++; } void process_jlcpcb(string[string] entry) { if (entry["loadstatus"] == "noload") { File::fprintf(stderr, "skipping part %v\n", entry); return; } string part_number; if (hash_test(entry, "jlcpcb_part_number") && entry["jlcpcb_part_number"] != "unknown") { part_number = entry["jlcpcb_part_number"]; } else { error("Component (%s, %s, %s) has no JLCPCB Part #\n", entry["device"], entry["value"], entry["footprint"]); part_number = "unknown"; } static bool start = true; if (start) { File::fprintf (output, "Comment,Designator,Footprint,JLCPCB Part #\n"); start = false; } File::fprintf (output, "%s,", quoted(entry["value"])); string[*] refdes = String::wordsplit(entry["refdes"], " \t"); for (int i = 0; i < dim(refdes); i++) { File::fprintf (output, "%s", quoted(refdes[i])); if (i < dim(refdes) - 1) File::fprintf (output, " "); } File::fprintf (output, "%s,%s\n", quoted(entry["footprint"]), quoted(part_number)); } void process_digikey(string[string] entry) { if (entry["loadstatus"] == "noload") return; File::fprintf (output, "%s,%s,%s %s\n", entry["quantity"], quoted(part_number(entry)), quoted(entry["device"]), quoted(entry["value"])); } void process_mouser(string[string] entry) { if (entry["loadstatus"] == "noload") return; /* File::fprintf (output, "%s|%s\n", part_number(entry), entry["quantity"]); */ static bool start = true; if (start) { File::fprintf (output, "Mouser Part Number,Manufacturer Part Number,Quantity\n"); start = false; } File::fprintf (output, "%s,%s,%s\n", quoted(entry["vendor_part_number"]), quoted(entry["mfg_part_number"]), entry["quantity"]); } void process_other(string[string] entry) { if (entry["loadstatus"] == "noload") return; File::fprintf (output, "%s,%s,%s,%s %s\n", entry["vendor"], entry["quantity"], quoted(part_number(entry)), quoted(entry["device"]), entry["value"]); } void process_file(file f) { read_header(f); while (!File::end(f)) { string[string] entry = read_entry(f); string vendor = entry["vendor"]; if (!is_uninit(&vendors) && has_vendor(vendors, "seeed")) { process_seeed(entry); } else if (!is_uninit(&vendors) && has_vendor(vendors, "goldphoenix")) { process_goldphoenix(entry); } else if (!is_uninit(&vendors) && has_vendor(vendors, "jlcpcb")) { process_jlcpcb(entry); } else if ((!is_uninit(&vendors) && has_vendor(vendors, vendor)) || (!is_uninit(¬_vendors) && !has_vendor(not_vendors, vendor))) { switch (entry["vendor"]) { case "digikey": process_digikey(entry); break; case "mouser": process_mouser(entry); break; default: process_other(entry); break; } } } } ParseArgs::argdesc argd = { .args = { { .var = { .arg_flag = &mfg_part }, .abbr = 'm', .name = "mfg", .desc = "Display manufacturer part number"}, { .var = { .arg_flag = &warn_only }, .abbr = 'w', .name = "warn-only", .desc = "Missing values are warnings rather than errors"}, { .var = { .arg_string = &vendor_list }, .abbr = 'v', .name = "vendor", .expr_name = "vendors", .desc = "Vendors to match"}, { .var = { .arg_string = ¬_vendor_list }, .abbr = 'n', .name = "not-vendor", .expr_name = "not-vendor", .desc = "Vendors to exclude"}, { .var = { .arg_string = &output_name }, .abbr = 'o', .name = "output", .expr_name = "output", .desc = "Output file name"}, }, .unknown = &argi, }; void cleanup(int status) { if (status != 0 && !is_uninit(&output_name)) File::unlink(output_name); } void main() { int status = 1; ParseArgs::parseargs(&argd, &argv); if (!is_uninit(&vendor_list)) vendors = String::parse_csv(vendor_list); if (!is_uninit(¬_vendor_list)) not_vendors = String::parse_csv(not_vendor_list); if (!is_uninit(&output_name)) output = File::open(output_name, "w"); twixt(; cleanup(status)) { if (!is_uninit(&argi)) { for (int i = argi; i < dim(argv); i++) twixt(file f = File::open(argv[i], "r"); File::close(f)) process_file(f); } else process_file(stdin); if (!saw_error) status = 0; } exit(status); } main();