command-t 1.7 → 1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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