cosmetix in tagdb dox; removed unused function
[k8muffin.git] / src / search.c
blob4a8f080ece5f485cb33f271e91cc0411e9c73a58
1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "common.h"
20 ////////////////////////////////////////////////////////////////////////////////
21 #ifdef NO_SEARCH_DBGLOG
22 # define sxdlogf(...) ((void)0)
23 #else
24 # define sxdlogf dlogf
25 #endif
28 ////////////////////////////////////////////////////////////////////////////////
29 static int opt_files_only_in_files = 1;
32 ////////////////////////////////////////////////////////////////////////////////
33 typedef struct {
34 const char *name;
35 fl_fileinfo_t *fi;
36 UT_hash_handle hh;
37 } filename_t;
40 static filename_t *sname_hash = NULL;
41 static filename_t *sname_data = NULL;
44 typedef struct {
45 const char *value;
46 fl_tagvalue_t *tv;
47 UT_hash_handle hh;
48 } tagvalue_t;
50 // all tags
51 static tagvalue_t *tval_hash = NULL;
52 static tagvalue_t *tval_data = NULL;
54 static tagvalue_t *tspec_hash[TFL_MAX] = {NULL};
55 static tagvalue_t *tspec_data[TFL_MAX] = {NULL};
58 static fl_tagvalue_t *get_tag_by_name (const char *str, tagvalue_t **hash) {
59 if (str != NULL && str[0]) {
60 tagvalue_t *tv;
61 HASH_FIND_STR(*hash, str, tv);
62 return (tv != NULL ? tv->tv : NULL);
64 return NULL;
68 #define XCLR(var) do { \
69 HASH_CLEAR(hh, var##_hash); \
70 if (var##_data != NULL) free(var##_data); \
71 var##_data = NULL; \
72 var##_hash = NULL; \
73 } while (0)
75 static void clear_hashes (void) {
76 XCLR(sname);
77 XCLR(tval);
78 for (int f = 0; f < TFL_MAX; ++f) {
79 HASH_CLEAR(hh, tspec_hash[f]);
80 if (tspec_data[f] != NULL) free(tspec_data[f]);
81 tspec_hash[f] = NULL;
82 tspec_data[f] = NULL;
86 #undef XCLR
89 // allocate full chunks, it's faster and better than malloc()ing each item separately
90 static void build_hashes (void) {
91 sname_data = malloc(sizeof(sname_data[0])*file_count);
92 for (uint32_t f = 0; f < file_count; ++f) {
93 fl_fileinfo_t *fi = get_fi(f);
94 filename_t *fn = &sname_data[f];
95 fn->name = get_str(fi->shortname);
96 fn->fi = fi;
97 HASH_ADD_KEYPTR(hh, sname_hash, fn->name, strlen(fn->name), fn);
99 printf("%d snames in hash\n", HASH_COUNT(sname_hash));
101 tval_data = malloc(sizeof(tval_data[0])*tag_count);
102 for (uint32_t f = 0; f < tag_count; ++f) {
103 fl_tagvalue_t *tv = get_tv(f);
104 tagvalue_t *t = &tval_data[f];
105 //if (tv->value >= string_bytes) abort();
106 t->value = get_str(tv->value);
107 t->tv = tv;
108 HASH_ADD_KEYPTR(hh, tval_hash, t->value, strlen(t->value), t);
110 printf("%d tags in hash\n", HASH_COUNT(tval_hash));
112 int spcnt[TFL_MAX];
113 for (int f = 0; f < TFL_MAX; ++f) {
114 spcnt[f] = 0;
115 tspec_data[f] = malloc(sizeof(tspec_data[f][0])*tag_count); //FIXME: too much
117 for (uint32_t f = 0; f < file_count; ++f) {
118 //const fl_fileinfo_t *fi = get_fi_idx(f);
119 for (int tn = 0; tn < TFL_MAX; ++tn) {
120 tagvalue_t *tv;
121 fl_tagvalue_t *ftv = get_tv(get_fi(f)->tags[tn]);
122 const char *str = get_str(ftv->value);
123 HASH_FIND_STR(tspec_hash[tn], str, tv);
124 if (tv == NULL) {
125 tv = &tspec_data[tn][spcnt[tn]++];
126 tv->value = str;
127 tv->tv = ftv;
128 HASH_ADD_KEYPTR(hh, tspec_hash[tn], tv->value, strlen(tv->value), tv);
135 ////////////////////////////////////////////////////////////////////////////////
136 #define ARRAYLEN(arr) (sizeof((arr))/sizeof((arr)[0]))
139 typedef enum {
140 SPT_ANY,
141 SPT_ORIG,
142 SPT_TRANS
143 } special_dir_type;
146 static const special_dir_type tagtype[TFL_MAX] = {
147 SPT_ANY, // TFL_YEAR
148 SPT_ORIG, // TFL_ARTIST_O
149 SPT_TRANS, // TFL_ARTIST_T
150 SPT_ORIG, // TFL_ALBUM_O
151 SPT_TRANS, // TFL_ALBUM_T
152 SPT_ORIG, // TFL_TITLE_O
153 SPT_TRANS, // TFL_TITLE_T
154 SPT_ORIG, // TFL_GENRE_O
155 SPT_TRANS, // TFL_GENRE_T
159 typedef struct {
160 const char *name; // with colon
161 special_dir_type type;
162 int tflidx;
163 } special_dir_info_t;
166 static const special_dir_info_t spec_dirs[] = {
167 {.name=":oartist", .type=SPT_ORIG, .tflidx=TFL_ARTIST_O},
168 {.name=":oalbum", .type=SPT_ORIG, .tflidx=TFL_ALBUM_O},
169 {.name=":otitle", .type=SPT_ORIG, .tflidx=TFL_TITLE_O},
170 //{.name=":ogenre", .type=SPT_ORIG, .tflidx=TFL_GENRE_O},
171 {.name=":year", .type=SPT_ANY, .tflidx=TFL_YEAR},
172 {.name=":artist", .type=SPT_TRANS, .tflidx=TFL_ARTIST_T},
173 {.name=":album", .type=SPT_TRANS, .tflidx=TFL_ALBUM_T},
174 {.name=":title", .type=SPT_TRANS, .tflidx=TFL_TITLE_T},
175 //{.name=":genre", .type=SPT_TRANS, .tflidx=TFL_GENRE_T},
179 ////////////////////////////////////////////////////////////////////////////////
180 static inline const special_dir_info_t *get_special_by_name (const char *name) {
181 if (name != NULL && name[0] == ':') {
182 for (size_t f = 0; f < ARRAYLEN(spec_dirs); ++f) {
183 if (strcmp(spec_dirs[f].name, name) == 0) return &spec_dirs[f];
186 return NULL;
190 static inline fl_tagvalue_t *get_tag_with_special (const char *name, const special_dir_info_t *sp) {
191 return (sp != NULL ? get_tag_by_name(name, &tspec_hash[sp->tflidx]) : get_tag_by_name(name, &tval_hash));
195 ////////////////////////////////////////////////////////////////////////////////
196 #define FIDX_NONE (0)
198 typedef struct {
199 const char *name; // short names
200 uint32_t fidx; // file index+1 (if any) or FIDX_NONE
201 } diritem_t;
203 typedef struct {
204 diritem_t *items;
205 int fcount;
206 } dirinfo_t;
209 typedef struct {
210 uint32_t idx;
211 UT_hash_handle hh;
212 } u32value_t;
215 static inline int zdifsign (uint32_t n0, uint32_t n1) {
216 return (n0 < n1 ? -1 : (n0 > n1 ? 1 : 0));
220 // /tag/tag/tag/
221 // /:artist/name/:album/name/
222 static dirinfo_t *list_dir_simple (const char *path) {
223 u32value_t *files = NULL; // NULL: all files in list
224 u32value_t *fhash = NULL; // to ease searching by id
225 uint32_t pos = 0;
226 int onlyfiles = 0;
227 special_dir_type sptype = SPT_ANY;
228 const special_dir_info_t *sdi = NULL;
229 char *condstr = alloca(strlen(path)+1);
230 u32value_t *tl_hash = NULL, *hx, *thx;
231 dirinfo_t *di;
232 int firsttime = 1;
234 //TODO: accelerate this with hash
235 void add_from_tv (const fl_tagvalue_t *tv, const special_dir_info_t *sp, special_dir_type tp) {
236 // just add all files
237 uint32_t cnt = 0;
238 //sxdlogf("add_from_tv: [%s]; sp=%p; tp=%d\n", get_str(tv->value), sp, tp);
239 // let it be MANY
240 for (int tn = 0; tn < TFL_MAX; ++tn) {
241 //sxdlogf(" tn=%d; count=%u\n", tn, tv->tfl[tn].count);
242 cnt += tv->tfl[tn].count;
244 files = malloc(sizeof(files[0])*cnt+1);
245 cnt = 0;
246 for (int tn = 0; tn < TFL_MAX; ++tn) {
247 const fl_tfl_t *tfl;
248 u32value_t *fl;
249 if (sp != NULL && tn != sp->tflidx) continue;
250 if (sp == NULL && tp != SPT_ANY && tagtype[tn] != SPT_ANY && tp != tagtype[tn]) continue;
251 tfl = &tv->tfl[tn];
252 //sxdlogf(" *:tn=%d; count=%u\n", tn, tfl->count);
253 for (uint32_t f = 0; f < tfl->count; ++f) {
254 uint32_t idx = tfl_data[tfl->idx+f];
255 //sxdlogf(" %u: idx=%u\n", f, tfl_data[tfl->idx+f]);
256 HASH_FIND_UINT32(fhash, &idx, fl);
257 if (fl == NULL) {
258 fl = &files[cnt++];
259 fl->idx = idx;
260 HASH_ADD_UINT32(fhash, idx, fl);
266 void remove_not_have_tv (const fl_tagvalue_t *tv, const special_dir_info_t *sp, special_dir_type tp) {
267 // remove all files that have no such tag in given field
268 u32value_t *cf, *cftmp;
269 HASH_ITER(hh, fhash, cf, cftmp) {
270 const fl_fileinfo_t *fi = get_fi(cf->idx);
271 int dodrop = 1;
272 for (int tn = 0; tn < TFL_MAX; ++tn) {
273 if (sp != NULL && tn != sp->tflidx) continue;
274 if (sp == NULL && tp != SPT_ANY && tagtype[tn] != SPT_ANY && tp != tagtype[tn]) continue;
275 //sxdlogf("checking (%d)[%s]: %u : %u\n", tn, get_str(fi->shortname), fi->tags[tn], tv2idx(tv));
276 if (fi->tags[tn] == tv2idx(tv)) { dodrop = 0; break; }
278 if (dodrop) HASH_DEL(fhash, cf);
282 di = calloc(1, sizeof(*di));
284 if (strcmp(path, "/") == 0 || strcmp(path, "/:ttags") == 0 || strcmp(path, "/:otags/:ttags") == 0) {
285 doroot:
286 // wow! list ALL TAGS here!
287 di = calloc(1, sizeof(*di));
288 di->fcount = t_tag_count+ARRAYLEN(spec_dirs)+2;
289 di->items = calloc(di->fcount, sizeof(di->items[0]));
291 di->items[pos++].name = ":files";
292 di->items[pos++].name = ":otags";
294 for (int f = 0; f < ARRAYLEN(spec_dirs); ++f) if (spec_dirs[f].type == SPT_TRANS || spec_dirs[f].type == SPT_ANY) {
295 di->items[pos++].name = spec_dirs[f].name;
297 for (uint32_t f = 0; f < t_tag_count; ++f) {
298 if (get_str(get_tv(taglistt_data[f])->value)[0] == 0) {
299 printf("tags=%u; idx=%u; ssize=%u; sofs=%u\n", tag_count, taglistt_data[f], string_bytes, get_tv(taglistt_data[f])->value);
300 for (uint32_t fn = 0; fn < file_count; ++fn) {
301 for (int tn = 0; tn < TFL_MAX; ++tn) {
302 if (get_fi(fn)->tags[tn] == taglistt_data[f]) {
303 printf("tag %d of file [%s] [%s] is empty!\n", tn, get_str(get_fi(fn)->realname), get_str(get_fi(fn)->shortname));
307 abort();
309 di->items[pos++].name = get_str(get_tv(taglistt_data[f])->value);
311 di->fcount = pos;
312 goto ret_sort_di;
315 if (strcmp(path, "/:otags") == 0 || strcmp(path, "/:otags/:ttags") == 0) {
316 // wow! list ALL TAGS here!
317 di = calloc(1, sizeof(*di));
318 di->fcount = o_tag_count+ARRAYLEN(spec_dirs)+2;
319 di->items = calloc(di->fcount, sizeof(di->items[0]));
321 di->items[pos++].name = ":files";
322 di->items[pos++].name = ":ttags";
324 for (int f = 0; f < ARRAYLEN(spec_dirs); ++f) if (spec_dirs[f].type == SPT_ORIG || spec_dirs[f].type == SPT_ANY) {
325 di->items[pos++].name = spec_dirs[f].name;
327 for (uint32_t f = 0; f < o_tag_count; ++f) di->items[pos++].name = get_str(get_tv(taglisto_data[f])->value);
328 di->fcount = pos;
329 goto ret_sort_di;
332 sxdlogf("path: [%s]\n", path);
333 // select and then refine
334 while (*path) {
335 fl_tagvalue_t *tv;
336 while (*path == '/') ++path;
337 if (!path[0]) break;
339 char *e = strchr(path, '/');
340 if (e == NULL) {
341 strcpy(condstr, path);
342 path += strlen(path);
343 } else {
344 memcpy(condstr, path, e-path);
345 condstr[e-path] = 0;
346 path = e+1;
349 sxdlogf("(fcount=%d) condstr: [%s]; sptype: %d\n", HASH_COUNT(fhash), condstr, sptype);
350 if (condstr[0] == ':') {
351 // specific tag
352 if (strcmp(condstr, ":and") == 0 || strcmp(condstr, ":or") == 0) {
353 if (firsttime) goto doroot;
354 break;
356 firsttime = 0;
358 if (sdi != NULL) { sxdlogf("error: 2 specials\n"); HASH_CLEAR(hh, fhash); break; } // error
360 if (strcmp(condstr, ":files") == 0) {
361 // we want only files
362 onlyfiles = 1;
363 if (files == NULL) {
364 // wow, all files!
365 files = malloc(sizeof(files[0])*file_count);
366 for (uint32_t f = 0; f < file_count; ++f) {
367 u32value_t *fl = &files[f];
368 fl->idx = f;
369 HASH_ADD_UINT32(fhash, idx, fl);
372 break;
375 if (strcmp(condstr, ":otags") == 0) {
376 if (sptype == SPT_ORIG) { sxdlogf(" error: 'otags' in tmode\n"); HASH_CLEAR(hh, fhash); break; } // error
377 sptype = SPT_ORIG;
378 sxdlogf("otags; path=[%s]\n", path);
379 continue;
382 if (strcmp(condstr, ":ttags") == 0) {
383 if (sptype == SPT_TRANS) { sxdlogf(" error: 'ttags' in tmode\n"); HASH_CLEAR(hh, fhash); break; } // error
384 sptype = SPT_TRANS;
385 sxdlogf("ttags; path=[%s]\n", path);
386 continue;
389 if ((sdi = get_special_by_name(condstr)) == NULL) {
390 sxdlogf("unknown special: [%s]\n", condstr);
391 HASH_CLEAR(hh, fhash);
392 break;
395 if (sdi->type != SPT_ANY && sptype != SPT_ANY) {
396 if (sdi->type != sptype) {
397 sxdlogf("bad special in mode %d: [%s]\n", sptype, condstr);
398 HASH_CLEAR(hh, fhash);
399 break;
403 sxdlogf("special: [%s] (fc=%d)\n", condstr, HASH_COUNT(fhash));
404 if (sdi->type != SPT_ANY) sptype = sdi->type;
406 continue;
408 firsttime = 0;
409 // got tag value
410 tv = get_tag_with_special(condstr, sdi);
411 if (tv == NULL) { sxdlogf(" for tag '%s': wtf?!\n", condstr); HASH_CLEAR(hh, fhash); break; } // no items, nothing can be found
412 if (files == NULL) {
413 // first time: add all files with this tag
414 sxdlogf("first time: sdi=%p; sptype=%d", sdi, sptype);
415 add_from_tv(tv, sdi, sptype);
416 } else {
417 // remove all files that have no such tag in given field
418 sxdlogf("filtering out: sdi=%p; sptype=%d", sdi, sptype);
419 remove_not_have_tv(tv, sdi, sptype);
421 sdi = NULL; // processed
423 // search complete
424 //di->fcount += 2; // "." and ".."
425 ++di->fcount; // :files
426 if (!onlyfiles) {
427 // specials
428 di->fcount += ARRAYLEN(spec_dirs);
429 if (sdi == NULL) {
430 // path ends with tag value
431 // build tag list
432 sxdlogf("sptype=%d\n", sptype);
433 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
434 for (size_t sn = 0; sn < ARRAYLEN(spec_dirs); ++sn) {
435 uint32_t idx;
436 if (sptype != SPT_ANY && spec_dirs[sn].type != SPT_ANY && spec_dirs[sn].type != sptype) continue;
437 idx = get_fi(cf->idx)->tags[spec_dirs[sn].tflidx];
438 HASH_FIND_UINT32(tl_hash, &idx, hx);
439 if (hx == NULL) {
440 hx = calloc(1, sizeof(*hx));
441 hx->idx = idx;
442 HASH_ADD_UINT32(tl_hash, idx, hx);
446 di->fcount += HASH_COUNT(tl_hash);
447 if (opt_files_only_in_files) HASH_CLEAR(hh, fhash);
448 } else {
449 // path ends with tag name: collect all from field
450 if (files == NULL) {
451 // collect all from full list
452 for (tagvalue_t *tv = tspec_hash[sdi->tflidx]; tv != NULL; tv = tv->hh.next) {
453 uint32_t idx = tv2idx(tv->tv);
454 HASH_FIND_UINT32(tl_hash, &idx, hx);
455 if (hx == NULL) {
456 hx = calloc(1, sizeof(*hx));
457 hx->idx = idx;
458 HASH_ADD_UINT32(tl_hash, idx, hx);
461 } else {
462 // collect from files
463 //sxdlogf("sptype=%d; tag '%s'; fcount=%d\n", sptype, condstr, fcount);
464 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
465 uint32_t idx = get_fi(cf->idx)->tags[sdi->tflidx];
466 HASH_FIND_UINT32(tl_hash, &idx, hx);
467 if (hx == NULL) {
468 hx = calloc(1, sizeof(*hx));
469 hx->idx = idx;
470 HASH_ADD_UINT32(tl_hash, idx, hx);
474 di->fcount += HASH_COUNT(tl_hash);
475 HASH_CLEAR(hh, fhash);
477 } else {
478 // only files
479 //di->fcount = HASH_COUNT(fhash);
482 di->fcount += HASH_COUNT(fhash);
483 di->items = calloc(di->fcount+1, sizeof(di->items[0]));
484 sxdlogf("allocated %d items; pos=%d\n", di->fcount+1, pos);
485 if (!onlyfiles) {
486 // specials
487 di->items[pos++].name = ":files";
488 for (size_t f = 0; f < ARRAYLEN(spec_dirs); ++f) {
489 if (sptype == SPT_ANY || spec_dirs[f].type == SPT_ANY || spec_dirs[f].type == sptype) {
490 di->items[pos++].name = spec_dirs[f].name;
493 // tags
494 HASH_ITER(hh, tl_hash, hx, thx) {
495 di->items[pos++].name = get_str(get_tv(hx->idx)->value);
496 HASH_DEL(tl_hash, hx);
497 free(hx);
500 // files
501 if (HASH_COUNT(fhash) > 0) {
502 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
503 if (pos >= di->fcount) { dlogf("FATAL: internal error in searcher!\n"); abort(); }
504 di->items[pos].fidx = cf->idx+1; //NOTE +1!
505 di->items[pos++].name = get_str(get_fi(cf->idx)->shortname);
508 di->fcount = pos;
509 HASH_CLEAR(hh, fhash);
510 if (files != NULL) free(files);
511 // sort files, so mplayer and others will see sorted dir
512 ret_sort_di:
513 qsort(di->items, di->fcount, sizeof(di->items[0]), lambda(int, (const void *p0, const void *p1) {
514 const diritem_t *i0 = (const diritem_t *)p0;
515 const diritem_t *i1 = (const diritem_t *)p1;
516 int sgn;
517 const fl_fileinfo_t *f0;
518 const fl_fileinfo_t *f1;
519 //sxdlogf("[%s] [%s]\n", s0, s1);
520 if (i0->name[0] == ':' && i1->name[0] != ':') return -1;
521 if (i0->name[0] != ':' && i1->name[0] == ':') return 1;
522 if (i0->fidx == 0) return (i1->fidx != 0 ? 1 : strcmp(i0->name, i1->name));
523 if (i1->fidx == 0) return (i0->fidx != 0 ? -1 : strcmp(i0->name, i1->name));
524 // compare files
525 f0 = get_fi(i0->fidx-1);
526 f1 = get_fi(i1->fidx-1);
527 // years
528 if (f0->year != f1->year && (f0->year != 0 || f1->year != 0)) {
529 sgn = zdifsign(f0->year, f1->year);
530 if (sgn) return sgn;
532 // artists
533 sgn = zdifsign(f0->artist_t, f1->artist_t);
534 if (sgn) return sgn;
535 // albums
536 sgn = zdifsign(f0->album_t, f1->album_t);
537 if (sgn) return sgn;
538 // track numbers
539 sgn = zdifsign(f0->tracknum, f1->tracknum);
540 if (sgn) return sgn;
541 // names
542 return strcmp(i0->name, i1->name);
543 }));
544 return di;
548 static dirinfo_t *list_dir_or (char *pt) {
549 for (;;) {
550 dirinfo_t *di;
551 int stop = 0;
552 char *ta = strstr(pt, "/:or/");
553 if (ta == NULL) return list_dir_simple(pt);
554 *ta = 0;
555 sxdlogf("OR: [%s]\n", pt);
556 di = list_dir_simple(pt);
557 *ta = '/';
558 pt = strchr(ta+1, '/');
559 // check if we have something except specials
560 for (int f = 0; f < di->fcount; ++f) if (di->items[f].name[0] != ':') { stop = 1; break; }
561 if (stop) return di;
562 free(di->items);
563 free(di);
568 typedef struct {
569 //fl_fileinfo_t **files;
570 const char *name; // short names
571 UT_hash_handle hh;
572 } nlist_t;
575 static dirinfo_t *list_dir (const char *path) {
576 char *pt;
577 nlist_t *nhash = NULL, *nm, *nmtmp;
578 dirinfo_t *res;
580 void fill_nhash (dirinfo_t *di) {
581 if (di != NULL) {
582 for (int f = 0; f < di->fcount; ++f) {
583 nlist_t *nm;
584 HASH_FIND_STR(nhash, di->items[f].name, nm);
585 if (nm == NULL) {
586 nm = calloc(1, sizeof(*nm));
587 nm->name = di->items[f].name;
588 HASH_ADD_KEYPTR(hh, nhash, nm->name, strlen(nm->name), nm);
591 free(di->items);
592 free(di);
596 if (strstr(path, "/:and/") == NULL && strstr(path, "/:or/") == NULL) return list_dir_simple(path);
597 pt = alloca(strlen(path)+1);
598 strcpy(pt, path);
599 for (;;) {
600 char *tn;
601 sxdlogf("***a: [%s]\n", pt);
602 tn = strstr(pt, "/:and/");
603 if (tn == NULL) {
604 sxdlogf("ALAST: [%s]\n", pt);
605 fill_nhash(list_dir_or(pt));
606 break;
608 *tn = 0;
609 sxdlogf("AND: pt=[%s]; tn=[/%s]\n", pt, tn+1);
610 fill_nhash(list_dir_or(pt));
611 pt = strchr(tn+1, '/');
612 sxdlogf("xxx: [%s]\n", pt);
614 // now build result from nhash
615 HASH_SORT(nhash, lambda(int, (const nlist_t *i0, const nlist_t *i1) {
616 if (i0->name[0] == ':' && i1->name[0] != ':') return -1;
617 if (i0->name[0] != ':' && i1->name[0] == ':') return 1;
618 return strcmp(i0->name, i1->name);
619 }));
620 res = calloc(1, sizeof(*res));
621 res->items = calloc(HASH_COUNT(nhash)+1, sizeof(res->items[0]));
622 HASH_ITER(hh, nhash, nm, nmtmp) {
623 res->items[res->fcount++].name = nm->name;
624 HASH_DEL(nhash, nm);
625 free(nm);
627 return res;