command-t 1.7 → 1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- # Copyright 2010 Wincent Colaiuta. All rights reserved.
1
+ # Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
2
2
  #
3
3
  # Redistribution and use in source and binary forms, with or without
4
4
  # modification, are permitted provided that the following conditions are met:
@@ -21,4 +21,4 @@
21
21
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
22
  # POSSIBILITY OF SUCH DAMAGE.
23
23
 
24
- CFLAGS += -std=c99 -Wall -Wextra -Wno-unused-parameter
24
+ CFLAGS += -Wall -Wextra -Wno-unused-parameter
Binary file
@@ -1,4 +1,4 @@
1
- // Copyright 2010-2013 Wincent Colaiuta. All rights reserved.
1
+ // Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
2
2
  //
3
3
  // Redistribution and use in source and binary forms, with or without
4
4
  // modification, are permitted provided that the following conditions are met:
@@ -22,15 +22,19 @@
22
22
  // POSSIBILITY OF SUCH DAMAGE.
23
23
 
24
24
  #include "matcher.h"
25
+ #include "watchman.h"
25
26
 
26
- VALUE mCommandT = 0; // module CommandT
27
- VALUE cCommandTMatcher = 0; // class CommandT::Matcher
27
+ VALUE mCommandT = 0; // module CommandT
28
+ VALUE cCommandTMatcher = 0; // class CommandT::Matcher
29
+ VALUE mCommandTWatchman = 0; // module CommandT::Watchman
30
+ VALUE mCommandTWatchmanUtils = 0; // module CommandT::Watchman::Utils
28
31
 
29
32
  VALUE CommandT_option_from_hash(const char *option, VALUE hash)
30
33
  {
34
+ VALUE key;
31
35
  if (NIL_P(hash))
32
36
  return Qnil;
33
- VALUE key = ID2SYM(rb_intern(option));
37
+ key = ID2SYM(rb_intern(option));
34
38
  if (rb_funcall(hash, rb_intern("has_key?"), 1, key) == Qtrue)
35
39
  return rb_hash_aref(hash, key);
36
40
  else
@@ -44,8 +48,13 @@ void Init_ext()
44
48
 
45
49
  // class CommandT::Matcher
46
50
  cCommandTMatcher = rb_define_class_under(mCommandT, "Matcher", rb_cObject);
47
-
48
- // methods
49
51
  rb_define_method(cCommandTMatcher, "initialize", CommandTMatcher_initialize, -1);
50
52
  rb_define_method(cCommandTMatcher, "sorted_matches_for", CommandTMatcher_sorted_matches_for, -1);
53
+
54
+ // module CommandT::Watchman::Utils
55
+ mCommandTWatchman = rb_define_module_under(mCommandT, "Watchman");
56
+ mCommandTWatchmanUtils = rb_define_module_under(mCommandTWatchman, "Utils");
57
+ rb_define_singleton_method(mCommandTWatchmanUtils, "load", CommandTWatchmanUtils_load, 1);
58
+ rb_define_singleton_method(mCommandTWatchmanUtils, "dump", CommandTWatchmanUtils_dump, 1);
59
+ rb_define_singleton_method(mCommandTWatchmanUtils, "query", CommandTWatchmanUtils_query, 2);
51
60
  }
@@ -1,4 +1,4 @@
1
- // Copyright 2010-2013 Wincent Colaiuta. All rights reserved.
1
+ // Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
2
2
  //
3
3
  // Redistribution and use in source and binary forms, with or without
4
4
  // modification, are permitted provided that the following conditions are met:
@@ -23,8 +23,10 @@
23
23
 
24
24
  #include <ruby.h>
25
25
 
26
- extern VALUE mCommandT; // module CommandT
27
- extern VALUE cCommandTMatcher; // class CommandT::Matcher
26
+ extern VALUE mCommandT; // module CommandT
27
+ extern VALUE cCommandTMatcher; // class CommandT::Matcher
28
+ extern VALUE mCommandTWatchman; // module CommandT::Watchman
29
+ extern VALUE mCommandTWatchmanUtils; // module CommandT::Watchman::Utils
28
30
 
29
31
  // Encapsulates common pattern of checking for an option in an optional
30
32
  // options hash. The hash itself may be nil, but an exception will be
@@ -36,6 +36,16 @@ header('ruby.h')
36
36
  header('stdlib.h')
37
37
  header('string.h')
38
38
 
39
+ # optional headers (for CommandT::Watchman::Utils)
40
+ if have_header('fcntl.h') &&
41
+ have_header('sys/errno.h') &&
42
+ have_header('sys/socket.h')
43
+ RbConfig::MAKEFILE_CONFIG['DEFS'] += ' -DWATCHMAN_BUILD'
44
+
45
+ have_header('ruby/st.h') # >= 1.9; sets HAVE_RUBY_ST_H
46
+ have_header('st.h') # 1.8; sets HAVE_ST_H
47
+ end
48
+
39
49
  # optional
40
50
  if RbConfig::CONFIG['THREAD_MODEL'] == 'pthread'
41
51
  have_library('pthread', 'pthread_create') # sets HAVE_PTHREAD_H if found
@@ -48,6 +48,9 @@ double recursive_match(matchinfo_t *m, // sharable meta-data
48
48
  double seen_score = 0; // remember best score seen via recursion
49
49
  int dot_file_match = 0; // true if needle matches a dot-file
50
50
  int dot_search = 0; // true if searching for a dot
51
+ long i, j, distance;
52
+ int found;
53
+ double score_for_char;
51
54
 
52
55
  // do we have a memoized result we can return?
53
56
  double memoized = m->memo[needle_idx * m->needle_len + haystack_idx];
@@ -60,15 +63,15 @@ double recursive_match(matchinfo_t *m, // sharable meta-data
60
63
  goto memoize;
61
64
  }
62
65
 
63
- for (long i = needle_idx; i < m->needle_len; i++) {
66
+ for (i = needle_idx; i < m->needle_len; i++) {
64
67
  char c = m->needle_p[i];
65
68
  if (c == '.')
66
69
  dot_search = 1;
67
- int found = 0;
70
+ found = 0;
68
71
 
69
72
  // similar to above, we'll stop iterating when we know we're too close
70
73
  // to the end of the string to possibly match
71
- for (long j = haystack_idx;
74
+ for (j = haystack_idx;
72
75
  j <= m->haystack_len - (m->needle_len - i);
73
76
  j++, haystack_idx++) {
74
77
  char d = m->haystack_p[j];
@@ -87,8 +90,8 @@ double recursive_match(matchinfo_t *m, // sharable meta-data
87
90
  dot_search = 0;
88
91
 
89
92
  // calculate score
90
- double score_for_char = m->max_score_per_char;
91
- long distance = j - last_idx;
93
+ score_for_char = m->max_score_per_char;
94
+ distance = j - last_idx;
92
95
 
93
96
  if (distance > 1) {
94
97
  double factor = 1.0;
@@ -152,6 +155,8 @@ void calculate_match(VALUE str,
152
155
  VALUE never_show_dot_files,
153
156
  match_t *out)
154
157
  {
158
+ long i, max;
159
+ double score;
155
160
  matchinfo_t m;
156
161
  m.haystack_p = RSTRING_PTR(str);
157
162
  m.haystack_len = RSTRING_LEN(str);
@@ -163,14 +168,14 @@ void calculate_match(VALUE str,
163
168
  m.never_show_dot_files = never_show_dot_files == Qtrue;
164
169
 
165
170
  // calculate score
166
- double score = 1.0;
171
+ score = 1.0;
167
172
 
168
173
  // special case for zero-length search string
169
174
  if (m.needle_len == 0) {
170
175
 
171
176
  // filter out dot files
172
177
  if (!m.always_show_dot_files) {
173
- for (long i = 0; i < m.haystack_len; i++) {
178
+ for (i = 0; i < m.haystack_len; i++) {
174
179
  char c = m.haystack_p[i];
175
180
 
176
181
  if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
@@ -183,7 +188,7 @@ void calculate_match(VALUE str,
183
188
 
184
189
  // prepare for memoization
185
190
  double memo[m.haystack_len * m.needle_len];
186
- for (long i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
191
+ for (i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
187
192
  memo[i] = DBL_MAX;
188
193
  m.memo = memo;
189
194
 
@@ -1,4 +1,4 @@
1
- // Copyright 2010-2013 Wincent Colaiuta. All rights reserved.
1
+ // Copyright 2010-2014 Wincent Colaiuta. All rights reserved.
2
2
  //
3
3
  // Redistribution and use in source and binary forms, with or without
4
4
  // modification, are permitted provided that the following conditions are met:
@@ -77,9 +77,12 @@ int cmp_score(const void *a, const void *b)
77
77
 
78
78
  VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
79
79
  {
80
- // process arguments: 1 mandatory, 1 optional
81
- VALUE scanner, options;
80
+ VALUE always_show_dot_files;
81
+ VALUE never_show_dot_files;
82
+ VALUE options;
83
+ VALUE scanner;
82
84
 
85
+ // process arguments: 1 mandatory, 1 optional
83
86
  if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
84
87
  options = Qnil;
85
88
  if (NIL_P(scanner))
@@ -88,8 +91,8 @@ VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
88
91
  rb_iv_set(self, "@scanner", scanner);
89
92
 
90
93
  // check optional options hash for overrides
91
- VALUE always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
92
- VALUE never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
94
+ always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
95
+ never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
93
96
 
94
97
  rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
95
98
  rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
@@ -110,8 +113,9 @@ typedef struct {
110
113
 
111
114
  void *match_thread(void *thread_args)
112
115
  {
116
+ long i;
113
117
  thread_args_t *args = (thread_args_t *)thread_args;
114
- for (long i = args->thread_index; i < args->path_count; i += args->thread_count) {
118
+ for (i = args->thread_index; i < args->path_count; i += args->thread_count) {
115
119
  VALUE path = RARRAY_PTR(args->paths)[i];
116
120
  calculate_match(path,
117
121
  args->abbrev,
@@ -126,9 +130,23 @@ void *match_thread(void *thread_args)
126
130
 
127
131
  VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
128
132
  {
129
- // process arguments: 1 mandatory, 1 optional
130
- VALUE abbrev, options;
133
+ long i, limit, path_count, thread_count;
134
+ #ifdef HAVE_PTHREAD_H
135
+ long err;
136
+ #endif
137
+ match_t *matches;
138
+ thread_args_t *thread_args;
139
+ VALUE abbrev;
140
+ VALUE always_show_dot_files;
141
+ VALUE limit_option;
142
+ VALUE never_show_dot_files;
143
+ VALUE options;
144
+ VALUE paths;
145
+ VALUE results;
146
+ VALUE scanner;
147
+ VALUE threads_option;
131
148
 
149
+ // process arguments: 1 mandatory, 1 optional
132
150
  if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
133
151
  options = Qnil;
134
152
  if (NIL_P(abbrev))
@@ -138,22 +156,21 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
138
156
  abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
139
157
 
140
158
  // check optional options has for overrides
141
- VALUE limit_option = CommandT_option_from_hash("limit", options);
142
- VALUE threads_option = CommandT_option_from_hash("threads", options);
159
+ limit_option = CommandT_option_from_hash("limit", options);
160
+ threads_option = CommandT_option_from_hash("threads", options);
143
161
 
144
162
  // get unsorted matches
145
- VALUE scanner = rb_iv_get(self, "@scanner");
146
- VALUE paths = rb_funcall(scanner, rb_intern("paths"), 0);
147
- VALUE always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
148
- VALUE never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
163
+ scanner = rb_iv_get(self, "@scanner");
164
+ paths = rb_funcall(scanner, rb_intern("paths"), 0);
165
+ always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
166
+ never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
149
167
 
150
- long path_count = RARRAY_LEN(paths);
151
- match_t *matches = malloc(path_count * sizeof(match_t));
168
+ path_count = RARRAY_LEN(paths);
169
+ matches = malloc(path_count * sizeof(match_t));
152
170
  if (!matches)
153
171
  rb_raise(rb_eNoMemError, "memory allocation failed");
154
172
 
155
- int err;
156
- long thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
173
+ thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
157
174
 
158
175
  #ifdef HAVE_PTHREAD_H
159
176
  #define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
@@ -164,10 +181,10 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
164
181
  rb_raise(rb_eNoMemError, "memory allocation failed");
165
182
  #endif
166
183
 
167
- thread_args_t *thread_args = malloc(sizeof(thread_args_t) * thread_count);
184
+ thread_args = malloc(sizeof(thread_args_t) * thread_count);
168
185
  if (!thread_args)
169
186
  rb_raise(rb_eNoMemError, "memory allocation failed");
170
- for (int i = 0; i < thread_count; i++) {
187
+ for (i = 0; i < thread_count; i++) {
171
188
  thread_args[i].thread_count = thread_count;
172
189
  thread_args[i].thread_index = i;
173
190
  thread_args[i].matches = matches;
@@ -186,16 +203,16 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
186
203
  } else {
187
204
  err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
188
205
  if (err != 0)
189
- rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", err);
206
+ rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
190
207
  }
191
208
  #endif
192
209
  }
193
210
 
194
211
  #ifdef HAVE_PTHREAD_H
195
- for (int i = 0; i < thread_count - 1; i++) {
212
+ for (i = 0; i < thread_count - 1; i++) {
196
213
  err = pthread_join(threads[i], NULL);
197
214
  if (err != 0)
198
- rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", err);
215
+ rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
199
216
  }
200
217
  free(threads);
201
218
  #endif
@@ -208,12 +225,12 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
208
225
  // for all other non-empty search strings, sort by score
209
226
  qsort(matches, path_count, sizeof(match_t), cmp_score);
210
227
 
211
- VALUE results = rb_ary_new();
228
+ results = rb_ary_new();
212
229
 
213
- long limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
230
+ limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
214
231
  if (limit == 0)
215
232
  limit = path_count;
216
- for (long i = 0; i < path_count && limit > 0; i++) {
233
+ for (i = 0; i < path_count && limit > 0; i++) {
217
234
  if (matches[i].score > 0.0) {
218
235
  rb_funcall(results, rb_intern("push"), 1, matches[i].path);
219
236
  limit--;
@@ -21,7 +21,7 @@
21
21
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
22
  # POSSIBILITY OF SUCH DAMAGE.
23
23
 
24
- require 'json'
24
+ require 'pathname'
25
25
  require 'socket'
26
26
  require 'command-t/vim'
27
27
  require 'command-t/vim/path_utilities'
@@ -43,24 +43,27 @@ module CommandT
43
43
  def paths
44
44
  @paths[@path] ||= begin
45
45
  ensure_cache_under_limit
46
- sockname = JSON[%x{watchman get-sockname}]['sockname']
46
+ sockname = Watchman::Utils.load(
47
+ %x{watchman --output-encoding=bser get-sockname}
48
+ )['sockname']
47
49
  raise WatchmanUnavailable unless $?.exitstatus.zero?
48
- socket = UNIXSocket.open(sockname) do |s|
49
- root = File.realpath(File.expand_path(@path))
50
- s.puts JSON.generate(['watch-list'])
51
- if !JSON[s.gets]['roots'].include?(root)
50
+
51
+ UNIXSocket.open(sockname) do |socket|
52
+ root = Pathname.new(@path).realpath.to_s
53
+ roots = Watchman::Utils.query(['watch-list'], socket)['roots']
54
+ if !roots.include?(root)
52
55
  # this path isn't being watched yet; try to set up watch
53
- s.puts JSON.generate(['watch', root])
56
+ result = Watchman::Utils.query(['watch', root], socket)
54
57
 
55
58
  # root_restrict_files setting may prevent Watchman from working
56
- raise WatchmanUnavailable if JSON[s.gets].has_key?('error')
59
+ raise WatchmanUnavailable if result.has_key?('error')
57
60
  end
58
61
 
59
- s.puts JSON.generate(['query', root, {
62
+ query = ['query', root, {
60
63
  'expression' => ['type', 'f'],
61
64
  'fields' => ['name'],
62
- }])
63
- paths = JSON[s.gets]
65
+ }]
66
+ paths = Watchman::Utils.query(query, socket)
64
67
 
65
68
  # could return error if watch is removed
66
69
  raise WatchmanUnavailable if paths.has_key?('error')
@@ -0,0 +1,660 @@
1
+ // Copyright 2014 Wincent Colaiuta. All rights reserved.
2
+ //
3
+ // Redistribution and use in source and binary forms, with or without
4
+ // modification, are permitted provided that the following conditions are met:
5
+ //
6
+ // 1. Redistributions of source code must retain the above copyright notice,
7
+ // this list of conditions and the following disclaimer.
8
+ // 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ // this list of conditions and the following disclaimer in the documentation
10
+ // and/or other materials provided with the distribution.
11
+ //
12
+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ // POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ #include "watchman.h"
25
+
26
+ #ifdef WATCHMAN_BUILD
27
+
28
+ #if defined(HAVE_RUBY_ST_H)
29
+ #include <ruby/st.h>
30
+ #elif defined(HAVE_ST_H)
31
+ #include <st.h>
32
+ #else
33
+ #error no st.h header found
34
+ #endif
35
+
36
+ #include <fcntl.h> /* for fcntl() */
37
+ #include <sys/errno.h> /* for errno */
38
+ #include <sys/socket.h> /* for recv(), MSG_PEEK */
39
+
40
+ typedef struct {
41
+ uint8_t *data; // payload
42
+ size_t cap; // total capacity
43
+ size_t len; // current length
44
+ } watchman_t;
45
+
46
+ // Forward declarations:
47
+ VALUE watchman_load(char **ptr, char *end);
48
+ void watchman_dump(watchman_t *w, VALUE serializable);
49
+
50
+ /**
51
+ * Inspect a ruby object (debugging aid)
52
+ */
53
+ #define ruby_inspect(obj) rb_funcall(rb_mKernel, rb_intern("p"), 1, obj)
54
+
55
+ /**
56
+ * Print `count` bytes of memory at `address` (debugging aid)
57
+ */
58
+ #define dump(address, count) \
59
+ do { \
60
+ for (int i = 0; i < count; i++) { \
61
+ printf("%02x ", ((unsigned char *)address)[i]); printf("\n"); \
62
+ } \
63
+ } while(0)
64
+
65
+ #define WATCHMAN_DEFAULT_STORAGE 4096
66
+
67
+ #define WATCHMAN_BINARY_MARKER "\x00\x01"
68
+ #define WATCHMAN_ARRAY_MARKER 0x00
69
+ #define WATCHMAN_HASH_MARKER 0x01
70
+ #define WATCHMAN_STRING_MARKER 0x02
71
+ #define WATCHMAN_INT8_MARKER 0x03
72
+ #define WATCHMAN_INT16_MARKER 0x04
73
+ #define WATCHMAN_INT32_MARKER 0x05
74
+ #define WATCHMAN_INT64_MARKER 0x06
75
+ #define WATCHMAN_FLOAT_MARKER 0x07
76
+ #define WATCHMAN_TRUE 0x08
77
+ #define WATCHMAN_FALSE 0x09
78
+ #define WATCHMAN_NIL 0x0a
79
+ #define WATCHMAN_TEMPLATE_MARKER 0x0b
80
+ #define WATCHMAN_SKIP_MARKER 0x0c
81
+
82
+ #define WATCHMAN_HEADER \
83
+ WATCHMAN_BINARY_MARKER \
84
+ "\x06" \
85
+ "\x00\x00\x00\x00\x00\x00\x00\x00"
86
+
87
+ static const char watchman_array_marker = WATCHMAN_ARRAY_MARKER;
88
+ static const char watchman_hash_marker = WATCHMAN_HASH_MARKER;
89
+ static const char watchman_string_marker = WATCHMAN_STRING_MARKER;
90
+ static const char watchman_true = WATCHMAN_TRUE;
91
+ static const char watchman_false = WATCHMAN_FALSE;
92
+ static const char watchman_nil = WATCHMAN_NIL;
93
+
94
+ /**
95
+ * Appends `len` bytes, starting at `data`, to the watchman_t struct `w`
96
+ *
97
+ * Will attempt to reallocate the underlying storage if it is not sufficient.
98
+ */
99
+ void watchman_append(watchman_t *w, const char *data, size_t len) {
100
+ if (w->len + len > w->cap) {
101
+ w->cap += w->len + WATCHMAN_DEFAULT_STORAGE;
102
+ REALLOC_N(w->data, uint8_t, w->cap);
103
+ }
104
+ memcpy(w->data + w->len, data, len);
105
+ w->len += len;
106
+ }
107
+
108
+ /**
109
+ * Allocate a new watchman_t struct
110
+ *
111
+ * The struct has a small amount of extra capacity preallocated, and a blank
112
+ * header that can be filled in later to describe the PDU.
113
+ */
114
+ watchman_t *watchman_init() {
115
+ watchman_t *w = ALLOC(watchman_t);
116
+ w->cap = WATCHMAN_DEFAULT_STORAGE;
117
+ w->len = 0;
118
+ w->data = ALLOC_N(uint8_t, WATCHMAN_DEFAULT_STORAGE);
119
+
120
+ watchman_append(w, WATCHMAN_HEADER, sizeof(WATCHMAN_HEADER) - 1);
121
+ return w;
122
+ }
123
+
124
+ /**
125
+ * Free a watchman_t struct `w` that was previously allocated with
126
+ * `watchman_init`
127
+ */
128
+ void watchman_free(watchman_t *w) {
129
+ xfree(w->data);
130
+ xfree(w);
131
+ }
132
+
133
+ /**
134
+ * Encodes and appends the integer `num` to `w`
135
+ */
136
+ void watchman_dump_int(watchman_t *w, int64_t num) {
137
+ char encoded[1 + sizeof(int64_t)];
138
+
139
+ if (num == (int8_t)num) {
140
+ encoded[0] = WATCHMAN_INT8_MARKER;
141
+ encoded[1] = (int8_t)num;
142
+ watchman_append(w, encoded, 1 + sizeof(int8_t));
143
+ } else if (num == (int16_t)num) {
144
+ encoded[0] = WATCHMAN_INT16_MARKER;
145
+ *(int16_t *)(encoded + 1) = (int16_t)num;
146
+ watchman_append(w, encoded, 1 + sizeof(int16_t));
147
+ } else if (num == (int32_t)num) {
148
+ encoded[0] = WATCHMAN_INT32_MARKER;
149
+ *(int32_t *)(encoded + 1) = (int32_t)num;
150
+ watchman_append(w, encoded, 1 + sizeof(int32_t));
151
+ } else {
152
+ encoded[0] = WATCHMAN_INT64_MARKER;
153
+ *(int64_t *)(encoded + 1) = (int64_t)num;
154
+ watchman_append(w, encoded, 1 + sizeof(int64_t));
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Encodes and appends the string `string` to `w`
160
+ */
161
+ void watchman_dump_string(watchman_t *w, VALUE string) {
162
+ watchman_append(w, &watchman_string_marker, sizeof(watchman_string_marker));
163
+ watchman_dump_int(w, RSTRING_LEN(string));
164
+ watchman_append(w, RSTRING_PTR(string), RSTRING_LEN(string));
165
+ }
166
+
167
+ /**
168
+ * Encodes and appends the double `num` to `w`
169
+ */
170
+ void watchman_dump_double(watchman_t *w, double num) {
171
+ char encoded[1 + sizeof(double)];
172
+ encoded[0] = WATCHMAN_FLOAT_MARKER;
173
+ *(double *)(encoded + 1) = num;
174
+ watchman_append(w, encoded, sizeof(encoded));
175
+ }
176
+
177
+ /**
178
+ * Encodes and appends the array `array` to `w`
179
+ */
180
+ void watchman_dump_array(watchman_t *w, VALUE array) {
181
+ long i;
182
+ watchman_append(w, &watchman_array_marker, sizeof(watchman_array_marker));
183
+ watchman_dump_int(w, RARRAY_LEN(array));
184
+ for (i = 0; i < RARRAY_LEN(array); i++) {
185
+ watchman_dump(w, rb_ary_entry(array, i));
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Helper method that encodes and appends a key/value pair (`key`, `value`) from
191
+ * a hash to the watchman_t struct passed in via `data`
192
+ */
193
+ int watchman_dump_hash_iterator(VALUE key, VALUE value, VALUE data) {
194
+ watchman_t *w = (watchman_t *)data;
195
+ watchman_dump_string(w, StringValue(key));
196
+ watchman_dump(w, value);
197
+ return ST_CONTINUE;
198
+ }
199
+
200
+ /**
201
+ * Encodes and appends the hash `hash` to `w`
202
+ */
203
+ void watchman_dump_hash(watchman_t *w, VALUE hash) {
204
+ watchman_append(w, &watchman_hash_marker, sizeof(watchman_hash_marker));
205
+ watchman_dump_int(w, RHASH_SIZE(hash));
206
+ rb_hash_foreach(hash, watchman_dump_hash_iterator, (VALUE)w);
207
+ }
208
+
209
+ /**
210
+ * Encodes and appends the serialized Ruby object `serializable` to `w`
211
+ *
212
+ * Examples of serializable objects include arrays, hashes, strings, numbers
213
+ * (integers, floats), booleans, and nil.
214
+ */
215
+ void watchman_dump(watchman_t *w, VALUE serializable) {
216
+ switch (TYPE(serializable)) {
217
+ case T_ARRAY:
218
+ return watchman_dump_array(w, serializable);
219
+ case T_HASH:
220
+ return watchman_dump_hash(w, serializable);
221
+ case T_STRING:
222
+ return watchman_dump_string(w, serializable);
223
+ case T_FIXNUM: // up to 63 bits
224
+ return watchman_dump_int(w, FIX2LONG(serializable));
225
+ case T_BIGNUM:
226
+ return watchman_dump_int(w, NUM2LL(serializable));
227
+ case T_FLOAT:
228
+ return watchman_dump_double(w, NUM2DBL(serializable));
229
+ case T_TRUE:
230
+ return watchman_append(w, &watchman_true, sizeof(watchman_true));
231
+ case T_FALSE:
232
+ return watchman_append(w, &watchman_false, sizeof(watchman_false));
233
+ case T_NIL:
234
+ return watchman_append(w, &watchman_nil, sizeof(watchman_nil));
235
+ default:
236
+ rb_raise(rb_eTypeError, "unsupported type");
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Extract and return the int encoded at `ptr`
242
+ *
243
+ * Moves `ptr` past the extracted int.
244
+ *
245
+ * Will raise an ArgumentError if extracting the int would take us beyond the
246
+ * end of the buffer indicated by `end`, or if there is no int encoded at `ptr`.
247
+ *
248
+ * @returns The extracted int
249
+ */
250
+ int64_t watchman_load_int(char **ptr, char *end) {
251
+ char *val_ptr = *ptr + sizeof(int8_t);
252
+ int64_t val = 0;
253
+
254
+ if (val_ptr >= end) {
255
+ rb_raise(rb_eArgError, "insufficient int storage");
256
+ }
257
+
258
+ switch (*ptr[0]) {
259
+ case WATCHMAN_INT8_MARKER:
260
+ if (val_ptr + sizeof(int8_t) > end) {
261
+ rb_raise(rb_eArgError, "overrun extracting int8_t");
262
+ }
263
+ val = *(int8_t *)val_ptr;
264
+ *ptr = val_ptr + sizeof(int8_t);
265
+ break;
266
+ case WATCHMAN_INT16_MARKER:
267
+ if (val_ptr + sizeof(int16_t) > end) {
268
+ rb_raise(rb_eArgError, "overrun extracting int16_t");
269
+ }
270
+ val = *(int16_t *)val_ptr;
271
+ *ptr = val_ptr + sizeof(int16_t);
272
+ break;
273
+ case WATCHMAN_INT32_MARKER:
274
+ if (val_ptr + sizeof(int32_t) > end) {
275
+ rb_raise(rb_eArgError, "overrun extracting int32_t");
276
+ }
277
+ val = *(int32_t *)val_ptr;
278
+ *ptr = val_ptr + sizeof(int32_t);
279
+ break;
280
+ case WATCHMAN_INT64_MARKER:
281
+ if (val_ptr + sizeof(int64_t) > end) {
282
+ rb_raise(rb_eArgError, "overrun extracting int64_t");
283
+ }
284
+ val = *(int64_t *)val_ptr;
285
+ *ptr = val_ptr + sizeof(int64_t);
286
+ break;
287
+ default:
288
+ rb_raise(rb_eArgError, "bad integer marker 0x%02x", (unsigned int)*ptr[0]);
289
+ break;
290
+ }
291
+
292
+ return val;
293
+ }
294
+
295
+ /**
296
+ * Reads and returns a string encoded in the Watchman binary protocol format,
297
+ * starting at `ptr` and finishing at or before `end`
298
+ */
299
+ VALUE watchman_load_string(char **ptr, char *end) {
300
+ if (*ptr >= end) {
301
+ rb_raise(rb_eArgError, "unexpected end of input");
302
+ }
303
+
304
+ if (*ptr[0] != WATCHMAN_STRING_MARKER) {
305
+ rb_raise(rb_eArgError, "not a number");
306
+ }
307
+
308
+ *ptr += sizeof(int8_t);
309
+ if (*ptr >= end) {
310
+ rb_raise(rb_eArgError, "invalid string header");
311
+ }
312
+
313
+ int64_t len = watchman_load_int(ptr, end);
314
+ if (len == 0) { // special case for zero-length strings
315
+ return rb_str_new2("");
316
+ } else if (*ptr + len > end) {
317
+ rb_raise(rb_eArgError, "insufficient string storage");
318
+ }
319
+
320
+ VALUE string = rb_str_new(*ptr, len);
321
+ *ptr += len;
322
+ return string;
323
+ }
324
+
325
+ /**
326
+ * Reads and returns a double encoded in the Watchman binary protocol format,
327
+ * starting at `ptr` and finishing at or before `end`
328
+ */
329
+ double watchman_load_double(char **ptr, char *end) {
330
+ *ptr += sizeof(int8_t); // caller has already verified the marker
331
+ if (*ptr + sizeof(double) > end) {
332
+ rb_raise(rb_eArgError, "insufficient double storage");
333
+ }
334
+ double val = *(double *)*ptr;
335
+ *ptr += sizeof(double);
336
+ return val;
337
+ }
338
+
339
+ /**
340
+ * Helper method which returns length of the array encoded in the Watchman
341
+ * binary protocol format, starting at `ptr` and finishing at or before `end`
342
+ */
343
+ int64_t watchman_load_array_header(char **ptr, char *end) {
344
+ if (*ptr >= end) {
345
+ rb_raise(rb_eArgError, "unexpected end of input");
346
+ }
347
+
348
+ // verify and consume marker
349
+ if (*ptr[0] != WATCHMAN_ARRAY_MARKER) {
350
+ rb_raise(rb_eArgError, "not an array");
351
+ }
352
+ *ptr += sizeof(int8_t);
353
+
354
+ // expect a count
355
+ if (*ptr + sizeof(int8_t) * 2 > end) {
356
+ rb_raise(rb_eArgError, "incomplete array header");
357
+ }
358
+ return watchman_load_int(ptr, end);
359
+ }
360
+
361
+ /**
362
+ * Reads and returns an array encoded in the Watchman binary protocol format,
363
+ * starting at `ptr` and finishing at or before `end`
364
+ */
365
+ VALUE watchman_load_array(char **ptr, char *end) {
366
+ int64_t count, i;
367
+ VALUE array;
368
+
369
+ count = watchman_load_array_header(ptr, end);
370
+ array = rb_ary_new2(count);
371
+
372
+ for (i = 0; i < count; i++) {
373
+ rb_ary_push(array, watchman_load(ptr, end));
374
+ }
375
+
376
+ return array;
377
+ }
378
+
379
+ /**
380
+ * Reads and returns a hash encoded in the Watchman binary protocol format,
381
+ * starting at `ptr` and finishing at or before `end`
382
+ */
383
+ VALUE watchman_load_hash(char **ptr, char *end) {
384
+ int64_t count, i;
385
+ VALUE hash, key, value;
386
+
387
+ *ptr += sizeof(int8_t); // caller has already verified the marker
388
+
389
+ // expect a count
390
+ if (*ptr + sizeof(int8_t) * 2 > end) {
391
+ rb_raise(rb_eArgError, "incomplete hash header");
392
+ }
393
+ count = watchman_load_int(ptr, end);
394
+
395
+ hash = rb_hash_new();
396
+
397
+ for (i = 0; i < count; i++) {
398
+ key = watchman_load_string(ptr, end);
399
+ value = watchman_load(ptr, end);
400
+ rb_hash_aset(hash, key, value);
401
+ }
402
+
403
+ return hash;
404
+ }
405
+
406
+ /**
407
+ * Reads and returns a templated array encoded in the Watchman binary protocol
408
+ * format, starting at `ptr` and finishing at or before `end`
409
+ *
410
+ * Templated arrays are arrays of hashes which have repetitive key information
411
+ * pulled out into a separate "headers" prefix.
412
+ *
413
+ * @see https://github.com/facebook/watchman/blob/master/BSER.markdown
414
+ */
415
+ VALUE watchman_load_template(char **ptr, char *end) {
416
+ int64_t header_items_count, i, row_count;
417
+ VALUE array, hash, header, key, value;
418
+
419
+ *ptr += sizeof(int8_t); // caller has already verified the marker
420
+
421
+ // process template header array
422
+ header_items_count = watchman_load_array_header(ptr, end);
423
+ header = rb_ary_new2(header_items_count);
424
+ for (i = 0; i < header_items_count; i++) {
425
+ rb_ary_push(header, watchman_load_string(ptr, end));
426
+ }
427
+
428
+ // process row items
429
+ row_count = watchman_load_int(ptr, end);
430
+ array = rb_ary_new2(header_items_count);
431
+ while (row_count--) {
432
+ hash = rb_hash_new();
433
+ for (i = 0; i < header_items_count; i++) {
434
+ if (*ptr >= end) {
435
+ rb_raise(rb_eArgError, "unexpected end of input");
436
+ }
437
+
438
+ if (*ptr[0] == WATCHMAN_SKIP_MARKER) {
439
+ *ptr += sizeof(uint8_t);
440
+ } else {
441
+ value = watchman_load(ptr, end);
442
+ key = rb_ary_entry(header, i);
443
+ rb_hash_aset(hash, key, value);
444
+ }
445
+ }
446
+ rb_ary_push(array, hash);
447
+ }
448
+ return array;
449
+ }
450
+
451
+ /**
452
+ * Reads and returns an object encoded in the Watchman binary protocol format,
453
+ * starting at `ptr` and finishing at or before `end`
454
+ */
455
+ VALUE watchman_load(char **ptr, char *end) {
456
+ if (*ptr >= end) {
457
+ rb_raise(rb_eArgError, "unexpected end of input");
458
+ }
459
+
460
+ switch (*ptr[0]) {
461
+ case WATCHMAN_ARRAY_MARKER:
462
+ return watchman_load_array(ptr, end);
463
+ case WATCHMAN_HASH_MARKER:
464
+ return watchman_load_hash(ptr, end);
465
+ case WATCHMAN_STRING_MARKER:
466
+ return watchman_load_string(ptr, end);
467
+ case WATCHMAN_INT8_MARKER:
468
+ case WATCHMAN_INT16_MARKER:
469
+ case WATCHMAN_INT32_MARKER:
470
+ case WATCHMAN_INT64_MARKER:
471
+ return LL2NUM(watchman_load_int(ptr, end));
472
+ case WATCHMAN_FLOAT_MARKER:
473
+ return rb_float_new(watchman_load_double(ptr, end));
474
+ case WATCHMAN_TRUE:
475
+ *ptr += 1;
476
+ return Qtrue;
477
+ case WATCHMAN_FALSE:
478
+ *ptr += 1;
479
+ return Qfalse;
480
+ case WATCHMAN_NIL:
481
+ *ptr += 1;
482
+ return Qnil;
483
+ case WATCHMAN_TEMPLATE_MARKER:
484
+ return watchman_load_template(ptr, end);
485
+ default:
486
+ rb_raise(rb_eTypeError, "unsupported type");
487
+ }
488
+
489
+ return Qnil; // keep the compiler happy
490
+ }
491
+
492
+ /**
493
+ * CommandT::Watchman::Utils.load(serialized)
494
+ *
495
+ * Converts the binary object, `serialized`, from the Watchman binary protocol
496
+ * format into a normal Ruby object.
497
+ */
498
+ VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) {
499
+ serialized = StringValue(serialized);
500
+ long len = RSTRING_LEN(serialized);
501
+ char *ptr = RSTRING_PTR(serialized);
502
+ char *end = ptr + len;
503
+
504
+ // expect at least the binary marker and a int8_t length counter
505
+ if ((size_t)len < sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) * 2) {
506
+ rb_raise(rb_eArgError, "undersized header");
507
+ }
508
+
509
+ if (memcmp(ptr, WATCHMAN_BINARY_MARKER, sizeof(WATCHMAN_BINARY_MARKER) - 1)) {
510
+ rb_raise(rb_eArgError, "missing binary marker");
511
+ }
512
+
513
+ // get size marker
514
+ ptr += sizeof(WATCHMAN_BINARY_MARKER) - 1;
515
+ uint64_t payload_size = watchman_load_int(&ptr, end);
516
+ if (!payload_size) {
517
+ rb_raise(rb_eArgError, "empty payload");
518
+ }
519
+
520
+ // sanity check length
521
+ if (ptr + payload_size != end) {
522
+ rb_raise(rb_eArgError, "payload size mismatch (%lu)", end - (ptr + payload_size));
523
+ }
524
+
525
+ VALUE loaded = watchman_load(&ptr, end);
526
+
527
+ // one more sanity check
528
+ if (ptr != end) {
529
+ rb_raise(rb_eArgError, "payload termination mismatch (%lu)", end - ptr);
530
+ }
531
+
532
+ return loaded;
533
+ }
534
+
535
+ /**
536
+ * CommandT::Watchman::Utils.dump(serializable)
537
+ *
538
+ * Converts the Ruby object, `serializable`, into a binary string in the
539
+ * Watchman binary protocol format.
540
+ *
541
+ * Examples of serializable objects include arrays, hashes, strings, numbers
542
+ * (integers, floats), booleans, and nil.
543
+ */
544
+ VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable) {
545
+ watchman_t *w = watchman_init();
546
+ watchman_dump(w, serializable);
547
+
548
+ // update header with final length information
549
+ uint64_t *len = (uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1);
550
+ *len = w->len - sizeof(WATCHMAN_HEADER) + 1;
551
+
552
+ // prepare final return value
553
+ VALUE serialized = rb_str_buf_new(w->len);
554
+ rb_str_buf_cat(serialized, (const char*)w->data, w->len);
555
+ watchman_free(w);
556
+ return serialized;
557
+ }
558
+
559
+ /**
560
+ * Helper method for raising a SystemCallError wrapping a lower-level error code
561
+ * coming from the `errno` global variable.
562
+ */
563
+ void watchman_raise_system_call_error(int number) {
564
+ VALUE error = INT2FIX(number);
565
+ rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError));
566
+ }
567
+
568
+ // How far we have to look to figure out the size of the PDU header
569
+ #define WATCHMAN_SNIFF_BUFFER_SIZE sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t)
570
+
571
+ // How far we have to peek, at most, to figure out the size of the PDU itself
572
+ #define WATCHMAN_PEEK_BUFFER_SIZE \
573
+ sizeof(WATCHMAN_BINARY_MARKER) - 1 + \
574
+ sizeof(WATCHMAN_INT64_MARKER) + \
575
+ sizeof(int64_t)
576
+
577
+ /**
578
+ * CommandT::Watchman::Utils.query(query, socket)
579
+ *
580
+ * Converts `query`, a Watchman query comprising Ruby objects, into the Watchman
581
+ * binary protocol format, transmits it over socket, and unserializes and
582
+ * returns the result.
583
+ */
584
+ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) {
585
+ int fileno = NUM2INT(rb_funcall(socket, rb_intern("fileno"), 0));
586
+
587
+ // do blocking I/O to simplify the following logic
588
+ int flags = fcntl(fileno, F_GETFL);
589
+ if (fcntl(fileno, F_SETFL, flags & ~O_NONBLOCK) == -1) {
590
+ rb_raise(rb_eRuntimeError, "unable to clear O_NONBLOCK flag");
591
+ }
592
+
593
+ // send the message
594
+ VALUE serialized = CommandTWatchmanUtils_dump(self, query);
595
+ long query_len = RSTRING_LEN(serialized);
596
+ ssize_t sent = send(fileno, RSTRING_PTR(serialized), query_len, 0);
597
+ if (sent == -1) {
598
+ watchman_raise_system_call_error(errno);
599
+ } else if (sent != query_len) {
600
+ rb_raise(rb_eRuntimeError, "expected to send %ld bytes but sent %ld",
601
+ query_len, sent);
602
+ }
603
+
604
+ // sniff to see how large the header is
605
+ int8_t peek[WATCHMAN_PEEK_BUFFER_SIZE];
606
+ ssize_t received = recv(fileno, peek, WATCHMAN_SNIFF_BUFFER_SIZE, MSG_PEEK | MSG_WAITALL);
607
+ if (received == -1) {
608
+ watchman_raise_system_call_error(errno);
609
+ } else if (received != WATCHMAN_SNIFF_BUFFER_SIZE) {
610
+ rb_raise(rb_eRuntimeError, "failed to sniff PDU header");
611
+ }
612
+
613
+ // peek at size of PDU
614
+ int8_t sizes[] = { 0, 0, 0, 1, 2, 4, 8 };
615
+ ssize_t peek_size = sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) +
616
+ sizes[peek[sizeof(WATCHMAN_BINARY_MARKER) - 1]];
617
+
618
+ received = recv(fileno, peek, peek_size, MSG_PEEK);
619
+ if (received == -1) {
620
+ watchman_raise_system_call_error(errno);
621
+ } else if (received != peek_size) {
622
+ rb_raise(rb_eRuntimeError, "failed to peek at PDU header");
623
+ }
624
+ int8_t *pdu_size_ptr = peek + sizeof(WATCHMAN_BINARY_MARKER) - sizeof(int8_t);
625
+ int64_t payload_size =
626
+ peek_size +
627
+ watchman_load_int((char **)&pdu_size_ptr, (char *)peek + peek_size);
628
+
629
+ // actually read the PDU
630
+ void *buffer = xmalloc(payload_size);
631
+ if (!buffer) {
632
+ rb_raise(rb_eNoMemError, "failed to allocate %lld bytes", payload_size);
633
+ }
634
+ received = recv(fileno, buffer, payload_size, MSG_WAITALL);
635
+ if (received == -1) {
636
+ watchman_raise_system_call_error(errno);
637
+ } else if (received != payload_size) {
638
+ rb_raise(rb_eRuntimeError, "failed to load PDU");
639
+ }
640
+ char *payload = buffer + peek_size;
641
+ VALUE loaded = watchman_load(&payload, payload + payload_size);
642
+ free(buffer);
643
+ return loaded;
644
+ }
645
+
646
+ #else /* don't build Watchman utils; supply stubs only*/
647
+
648
+ VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) {
649
+ rb_raise(rb_eRuntimeError, "unsupported operation");
650
+ }
651
+
652
+ VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable) {
653
+ rb_raise(rb_eRuntimeError, "unsupported operation");
654
+ }
655
+
656
+ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) {
657
+ rb_raise(rb_eRuntimeError, "unsupported operation");
658
+ }
659
+
660
+ #endif