Allow for embedded commas (and quotes) in part numbers
[hw/altusmetrum] / bin / partslist-vendor
1 #!/usr/bin/env nickle
2 #
3 # Convert CSV parts list into vendor import format
4 #
5 # Copyright © 2015 Keith Packard <keithp@keithp.com>, GPL v3+
6 #
7
8 bool    mfg_part = false;
9 string[*]       vendors;
10 string          vendor_list;
11 string[*]       not_vendors;
12 string          not_vendor_list;
13 string  input_name;
14 string  output_name;
15 string  program;
16 int     argi;
17 int     lineno = 0;
18
19 void fatal(string format, poly args ...)
20 {
21         File::fprintf(stderr, format, args...);
22         exit(1);
23 }
24
25 string[*] read_line(file f) {
26         lineno++;
27         string  line = fgets(f);
28
29         return String::parse_csv(line);
30 }
31
32 string[*] header;
33
34 string[*] required_elements = {
35         "quantity",
36         "vendor",
37         "vendor_part_number",
38         "mfg_part_number",
39         "device",
40         "value",
41         "refdes",
42         "loadstatus"
43 };
44
45 bool has_header_member(string member) {
46         for (int i = 0; i < dim(header); i++)
47                 if (header[i] == member)
48                         return true;
49         return false;
50 }
51
52 bool has_vendor(string[*] vendors, string vendor) {
53         for (int i = 0; i < dim(vendors); i++)
54                 if (vendors[i] == vendor)
55                         return true;
56         return false;
57 }
58
59 void read_header(file f) {
60         header = read_line(f);
61
62         for (int i = 0; i < dim(required_elements); i++)
63                 if (!has_header_member(required_elements[i]))
64                         fatal("Missing header element \"%s\"\n", required_elements[i]);
65 }
66
67 string[string] read_entry(file f) {
68         string[*]       elements = read_line(f);
69         string[string]  entry = {};
70
71         if (dim(header) != dim(elements))
72                 fatal("line %d: has %d instead of %d elements (%V)\n",
73                       lineno, dim(elements), dim(header), elements);
74
75         for (int i = 0; i < dim(header); i++) {
76                 if (elements[i] == "")
77                         elements[i] = "unknown";
78                 entry[header[i]] = elements[i];
79         }
80         return entry;
81 }
82
83 string part_number(string[string] entry)
84 {
85         if (mfg_part)
86                 return entry["mfg_part_number"];
87         else
88                 return entry["vendor_part_number"];
89 }
90
91 string quoted(string v)
92 {
93         if (String::index(v, "\"") >= 0 || String::index(v, ",") >= 0) {
94                 if (String::index(v, "\"") >= 0) {
95                         string ret = "\"";
96                         for (int i = 0; i < String::length(v); i++) {
97                                 if (v[i] == '"')
98                                         ret = ret + "\"";
99                                 ret = ret + String::new(v[i]);
100                         }
101                         ret = ret + "\"";
102                         return ret;
103                 } else {
104                         return "\"" + v + "\"";
105                 }
106         } else {
107                 return v;
108         }
109 }
110
111 void process_seeed(string[string] entry)
112 {
113         if (entry["loadstatus"] == "noload")
114                 return;
115
116         static bool start = true;
117         if (start) {
118                 printf("Part/Designator,Manufacturer Part Number/Seeed SKU, Quantity\n");
119                 start = false;
120         }
121
122         string[*] refdes = String::wordsplit(entry["refdes"], " \t");
123         if (dim(refdes) > 1)
124                 printf ("\"");
125         for (int i = 0; i < dim(refdes); i++) {
126                 printf("%s", refdes[i]);
127                 if (i < dim(refdes) - 1)
128                         printf (",");
129         }
130         if (dim(refdes) > 1)
131                 printf ("\"");
132         printf(",%s,%s\n", quoted(entry["mfg_part_number"]), entry["quantity"]);
133 }
134
135 void process_digikey(string[string] entry)
136 {
137         if (entry["loadstatus"] == "noload")
138                 return;
139         printf("%s,%s,%s %s\n",
140                entry["quantity"],
141                quoted(part_number(entry)),
142                quoted(entry["device"]),
143                quoted(entry["value"]));
144 }
145
146 void process_mouser(string[string] entry)
147 {
148         if (entry["loadstatus"] == "noload")
149                 return;
150 /*      printf("%s|%s\n", part_number(entry), entry["quantity"]); */
151
152         static bool start = true;
153
154         if (start) {
155                 printf("Mouser Part Number,Manufacturer Part Number,Quantity 1\n");
156                 start = false;
157         }
158
159         printf("%s,%s,%s\n",
160                quoted(entry["vendor_part_number"]),
161                quoted(entry["mfg_part_number"]),
162                entry["quantity"]);
163 }
164
165 void process_other(string[string] entry) {
166         if (entry["loadstatus"] == "noload")
167                 return;
168         printf("%s,%s,%s,%s %s\n",
169                entry["vendor"],
170                entry["quantity"],
171                quoted(part_number(entry)),
172                quoted(entry["device"]),
173                entry["value"]);
174 }
175
176 void process_file(file f) {
177         read_header(f);
178         while (!File::end(f)) {
179                 string[string] entry = read_entry(f);
180                 string vendor = entry["vendor"];
181                 if (!is_uninit(&vendors) && has_vendor(vendors, "seeed")) {
182                         process_seeed(entry);
183                 } else if ((!is_uninit(&vendors) && has_vendor(vendors, vendor)) ||
184                            (!is_uninit(&not_vendors) && !has_vendor(not_vendors, vendor))) {
185                         switch (entry["vendor"]) {
186                         case "digikey":
187                                 process_digikey(entry);
188                                 break;
189                         case "mouser":
190                                 process_mouser(entry);
191                                 break;
192                         default:
193                                 process_other(entry);
194                                 break;
195                         }
196                 }
197         }
198 }
199
200 ParseArgs::argdesc argd = {
201         .args = {
202                 { .var = { .arg_flag = &mfg_part },
203                   .abbr = 'm',
204                   .name = "mfg",
205                   .desc = "Display manufacturer part number"},
206                 { .var = { .arg_string = &vendor_list },
207                   .abbr = 'v',
208                   .name = "vendor",
209                   .expr_name = "vendors",
210                   .desc = "Vendors to match"},
211                 { .var = { .arg_string = &not_vendor_list },
212                   .abbr = 'n',
213                   .name = "not-vendor",
214                   .expr_name = "not-vendor",
215                   .desc = "Vendors to exclude"},
216         },
217         .unknown = &argi,
218 };
219
220 void main() {
221         ParseArgs::parseargs(&argd, &argv);
222         if (!is_uninit(&vendor_list))
223                 vendors = String::parse_csv(vendor_list);
224
225         if (!is_uninit(&not_vendor_list))
226                 not_vendors = String::parse_csv(not_vendor_list);
227
228         if (!is_uninit(&argi)) {
229                 for (int i = argi; i < dim(argv); i++)
230                         twixt(file f = File::open(argv[i], "r"); File::close(f))
231                                 process_file(f);
232         } else
233                 process_file(stdin);
234 }
235
236 main();