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