232 lines
7.3 KiB
C
232 lines
7.3 KiB
C
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
|
|
// Licensed under the terms of the BSD 2-clause license.
|
|
|
|
#include <stdlib.h> /* for qsort() */
|
|
#include <string.h> /* for strncmp() */
|
|
#include "matcher.h"
|
|
#include "match.h"
|
|
#include "ext.h"
|
|
#include "ruby_compat.h"
|
|
|
|
// order matters; we want this to be evaluated only after ruby.h
|
|
#ifdef HAVE_PTHREAD_H
|
|
#include <pthread.h> /* for pthread_create, pthread_join etc */
|
|
#endif
|
|
|
|
// comparison function for use with qsort
|
|
int cmp_alpha(const void *a, const void *b)
|
|
{
|
|
match_t a_match = *(match_t *)a;
|
|
match_t b_match = *(match_t *)b;
|
|
VALUE a_str = a_match.path;
|
|
VALUE b_str = b_match.path;
|
|
char *a_p = RSTRING_PTR(a_str);
|
|
long a_len = RSTRING_LEN(a_str);
|
|
char *b_p = RSTRING_PTR(b_str);
|
|
long b_len = RSTRING_LEN(b_str);
|
|
int order = 0;
|
|
|
|
if (a_len > b_len) {
|
|
order = strncmp(a_p, b_p, b_len);
|
|
if (order == 0)
|
|
order = 1; // shorter string (b) wins
|
|
} else if (a_len < b_len) {
|
|
order = strncmp(a_p, b_p, a_len);
|
|
if (order == 0)
|
|
order = -1; // shorter string (a) wins
|
|
} else {
|
|
order = strncmp(a_p, b_p, a_len);
|
|
}
|
|
|
|
return order;
|
|
}
|
|
|
|
// comparison function for use with qsort
|
|
int cmp_score(const void *a, const void *b)
|
|
{
|
|
match_t a_match = *(match_t *)a;
|
|
match_t b_match = *(match_t *)b;
|
|
|
|
if (a_match.score > b_match.score)
|
|
return -1; // a scores higher, a should appear sooner
|
|
else if (a_match.score < b_match.score)
|
|
return 1; // b scores higher, a should appear later
|
|
else
|
|
return cmp_alpha(a, b);
|
|
}
|
|
|
|
VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
VALUE always_show_dot_files;
|
|
VALUE never_show_dot_files;
|
|
VALUE options;
|
|
VALUE scanner;
|
|
|
|
// process arguments: 1 mandatory, 1 optional
|
|
if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
|
|
options = Qnil;
|
|
if (NIL_P(scanner))
|
|
rb_raise(rb_eArgError, "nil scanner");
|
|
|
|
rb_iv_set(self, "@scanner", scanner);
|
|
|
|
// check optional options hash for overrides
|
|
always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
|
|
never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
|
|
|
|
rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
|
|
rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
typedef struct {
|
|
int thread_count;
|
|
int thread_index;
|
|
int case_sensitive;
|
|
match_t *matches;
|
|
long path_count;
|
|
VALUE paths;
|
|
VALUE abbrev;
|
|
VALUE always_show_dot_files;
|
|
VALUE never_show_dot_files;
|
|
} thread_args_t;
|
|
|
|
void *match_thread(void *thread_args)
|
|
{
|
|
long i;
|
|
thread_args_t *args = (thread_args_t *)thread_args;
|
|
for (i = args->thread_index; i < args->path_count; i += args->thread_count) {
|
|
VALUE path = RARRAY_PTR(args->paths)[i];
|
|
calculate_match(path,
|
|
args->abbrev,
|
|
args->case_sensitive,
|
|
args->always_show_dot_files,
|
|
args->never_show_dot_files,
|
|
&args->matches[i]);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
long i, limit, path_count, thread_count;
|
|
#ifdef HAVE_PTHREAD_H
|
|
long err;
|
|
pthread_t *threads;
|
|
#endif
|
|
match_t *matches;
|
|
thread_args_t *thread_args;
|
|
VALUE abbrev;
|
|
VALUE case_sensitive;
|
|
VALUE always_show_dot_files;
|
|
VALUE limit_option;
|
|
VALUE never_show_dot_files;
|
|
VALUE options;
|
|
VALUE paths;
|
|
VALUE results;
|
|
VALUE scanner;
|
|
VALUE sort_option;
|
|
VALUE threads_option;
|
|
|
|
// process arguments: 1 mandatory, 1 optional
|
|
if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
|
|
options = Qnil;
|
|
if (NIL_P(abbrev))
|
|
rb_raise(rb_eArgError, "nil abbrev");
|
|
|
|
// check optional options has for overrides
|
|
case_sensitive = CommandT_option_from_hash("case_sensitive", options);
|
|
limit_option = CommandT_option_from_hash("limit", options);
|
|
threads_option = CommandT_option_from_hash("threads", options);
|
|
sort_option = CommandT_option_from_hash("sort", options);
|
|
|
|
abbrev = StringValue(abbrev);
|
|
if (case_sensitive != Qtrue)
|
|
abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
|
|
|
|
// get unsorted matches
|
|
scanner = rb_iv_get(self, "@scanner");
|
|
paths = rb_funcall(scanner, rb_intern("paths"), 0);
|
|
always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
|
|
never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
|
|
|
|
path_count = RARRAY_LEN(paths);
|
|
matches = malloc(path_count * sizeof(match_t));
|
|
if (!matches)
|
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
|
|
|
thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
|
|
|
|
#ifdef HAVE_PTHREAD_H
|
|
#define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
|
|
if (path_count < THREAD_THRESHOLD)
|
|
thread_count = 1;
|
|
threads = malloc(sizeof(pthread_t) * thread_count);
|
|
if (!threads)
|
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
|
#endif
|
|
|
|
thread_args = malloc(sizeof(thread_args_t) * thread_count);
|
|
if (!thread_args)
|
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
|
for (i = 0; i < thread_count; i++) {
|
|
thread_args[i].thread_count = thread_count;
|
|
thread_args[i].thread_index = i;
|
|
thread_args[i].case_sensitive = case_sensitive == Qtrue;
|
|
thread_args[i].matches = matches;
|
|
thread_args[i].path_count = path_count;
|
|
thread_args[i].paths = paths;
|
|
thread_args[i].abbrev = abbrev;
|
|
thread_args[i].always_show_dot_files = always_show_dot_files;
|
|
thread_args[i].never_show_dot_files = never_show_dot_files;
|
|
|
|
#ifdef HAVE_PTHREAD_H
|
|
if (i == thread_count - 1) {
|
|
#endif
|
|
// for the last "worker", we'll just use the main thread
|
|
(void)match_thread(&thread_args[i]);
|
|
#ifdef HAVE_PTHREAD_H
|
|
} else {
|
|
err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
|
|
if (err != 0)
|
|
rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_PTHREAD_H
|
|
for (i = 0; i < thread_count - 1; i++) {
|
|
err = pthread_join(threads[i], NULL);
|
|
if (err != 0)
|
|
rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
|
|
}
|
|
free(threads);
|
|
#endif
|
|
|
|
if (NIL_P(sort_option) || sort_option == Qtrue) {
|
|
if (RSTRING_LEN(abbrev) == 0 ||
|
|
(RSTRING_LEN(abbrev) == 1 && RSTRING_PTR(abbrev)[0] == '.'))
|
|
// alphabetic order if search string is only "" or "."
|
|
qsort(matches, path_count, sizeof(match_t), cmp_alpha);
|
|
else
|
|
// for all other non-empty search strings, sort by score
|
|
qsort(matches, path_count, sizeof(match_t), cmp_score);
|
|
}
|
|
|
|
results = rb_ary_new();
|
|
|
|
limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
|
|
if (limit == 0)
|
|
limit = path_count;
|
|
for (i = 0; i < path_count && limit > 0; i++) {
|
|
if (matches[i].score > 0.0) {
|
|
rb_funcall(results, rb_intern("push"), 1, matches[i].path);
|
|
limit--;
|
|
}
|
|
}
|
|
|
|
free(matches);
|
|
return results;
|
|
}
|