vines-services 0.1.0

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.
Files changed (69) hide show
  1. data/LICENSE +19 -0
  2. data/README +40 -0
  3. data/Rakefile +130 -0
  4. data/bin/vines-services +95 -0
  5. data/conf/config.rb +25 -0
  6. data/lib/vines/services/command/init.rb +209 -0
  7. data/lib/vines/services/command/restart.rb +14 -0
  8. data/lib/vines/services/command/start.rb +30 -0
  9. data/lib/vines/services/command/stop.rb +20 -0
  10. data/lib/vines/services/command/views.rb +26 -0
  11. data/lib/vines/services/component.rb +26 -0
  12. data/lib/vines/services/config.rb +105 -0
  13. data/lib/vines/services/connection.rb +120 -0
  14. data/lib/vines/services/controller/attributes_controller.rb +19 -0
  15. data/lib/vines/services/controller/base_controller.rb +99 -0
  16. data/lib/vines/services/controller/disco_info_controller.rb +61 -0
  17. data/lib/vines/services/controller/labels_controller.rb +17 -0
  18. data/lib/vines/services/controller/members_controller.rb +44 -0
  19. data/lib/vines/services/controller/messages_controller.rb +66 -0
  20. data/lib/vines/services/controller/probes_controller.rb +45 -0
  21. data/lib/vines/services/controller/services_controller.rb +81 -0
  22. data/lib/vines/services/controller/subscriptions_controller.rb +39 -0
  23. data/lib/vines/services/controller/systems_controller.rb +45 -0
  24. data/lib/vines/services/controller/transfers_controller.rb +58 -0
  25. data/lib/vines/services/controller/uploads_controller.rb +62 -0
  26. data/lib/vines/services/controller/users_controller.rb +127 -0
  27. data/lib/vines/services/core_ext/blather.rb +46 -0
  28. data/lib/vines/services/core_ext/couchrest.rb +33 -0
  29. data/lib/vines/services/indexer.rb +195 -0
  30. data/lib/vines/services/priority_queue.rb +94 -0
  31. data/lib/vines/services/roster.rb +70 -0
  32. data/lib/vines/services/storage/couchdb/fragment.rb +23 -0
  33. data/lib/vines/services/storage/couchdb/service.rb +170 -0
  34. data/lib/vines/services/storage/couchdb/system.rb +141 -0
  35. data/lib/vines/services/storage/couchdb/upload.rb +66 -0
  36. data/lib/vines/services/storage/couchdb/user.rb +137 -0
  37. data/lib/vines/services/storage/couchdb/vcard.rb +13 -0
  38. data/lib/vines/services/storage/couchdb.rb +157 -0
  39. data/lib/vines/services/storage.rb +33 -0
  40. data/lib/vines/services/throttle.rb +26 -0
  41. data/lib/vines/services/version.rb +7 -0
  42. data/lib/vines/services/vql/compiler.rb +94 -0
  43. data/lib/vines/services/vql/vql.citrus +115 -0
  44. data/lib/vines/services/vql/vql.rb +186 -0
  45. data/lib/vines/services.rb +71 -0
  46. data/test/config_test.rb +242 -0
  47. data/test/priority_queue_test.rb +23 -0
  48. data/test/storage/couchdb_test.rb +30 -0
  49. data/test/vql/compiler_test.rb +96 -0
  50. data/test/vql/vql_test.rb +233 -0
  51. data/web/coffeescripts/api.coffee +51 -0
  52. data/web/coffeescripts/commands.coffee +18 -0
  53. data/web/coffeescripts/files.coffee +315 -0
  54. data/web/coffeescripts/init.coffee +21 -0
  55. data/web/coffeescripts/services.coffee +356 -0
  56. data/web/coffeescripts/setup.coffee +503 -0
  57. data/web/coffeescripts/systems.coffee +371 -0
  58. data/web/images/default-service.png +0 -0
  59. data/web/images/linux.png +0 -0
  60. data/web/images/mac.png +0 -0
  61. data/web/images/run.png +0 -0
  62. data/web/images/windows.png +0 -0
  63. data/web/index.html +17 -0
  64. data/web/stylesheets/common.css +52 -0
  65. data/web/stylesheets/files.css +218 -0
  66. data/web/stylesheets/services.css +181 -0
  67. data/web/stylesheets/setup.css +117 -0
  68. data/web/stylesheets/systems.css +142 -0
  69. metadata +230 -0
@@ -0,0 +1,242 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines/services'
4
+ require 'minitest/autorun'
5
+
6
+ class ConfigTest < MiniTest::Unit::TestCase
7
+ def teardown
8
+ FileUtils.rm(Dir.glob('localhost-*.db'))
9
+ %w[data uploads].each do |dir|
10
+ FileUtils.remove_dir(dir) if File.exist?(dir)
11
+ end
12
+ end
13
+
14
+ def test_missing_host_raises
15
+ assert_raises(RuntimeError) do
16
+ Vines::Services::Config.new do
17
+ # missing domain
18
+ end
19
+ end
20
+ end
21
+
22
+ def test_multiple_domains_raises
23
+ assert_raises(RuntimeError) do
24
+ Vines::Services::Config.new do
25
+ host 'vines.wonderland.lit', 'vines.wonderland.lit' do
26
+ upstream 'localhost', 5347, 'secr3t'
27
+ storage 'couchdb' do
28
+ host 'localhost'
29
+ port 5984
30
+ database 'wonderland_lit'
31
+ tls false
32
+ username ''
33
+ password ''
34
+ index_dir '.'
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def test_configure
42
+ config = Vines::Services::Config.configure do
43
+ host 'vines.wonderland.lit' do
44
+ upstream 'localhost', 5347, 'secr3t'
45
+ storage 'couchdb' do
46
+ host 'localhost'
47
+ port 5984
48
+ database 'wonderland_lit'
49
+ tls false
50
+ username ''
51
+ password ''
52
+ index_dir '.'
53
+ end
54
+ end
55
+ end
56
+ refute_nil config
57
+ assert_same config, Vines::Services::Config.instance
58
+ end
59
+
60
+ def test_default_upload_directory
61
+ config = Vines::Services::Config.configure do
62
+ host 'vines.wonderland.lit' do
63
+ upstream 'localhost', 5347, 'secr3t'
64
+ storage 'couchdb' do
65
+ host 'localhost'
66
+ port 5984
67
+ database 'wonderland_lit'
68
+ tls false
69
+ username ''
70
+ password ''
71
+ index_dir '.'
72
+ end
73
+ end
74
+ end
75
+ assert File.exist?('data/upload')
76
+ end
77
+
78
+ def test_custom_upload_directory
79
+ config = Vines::Services::Config.configure do
80
+ host 'vines.wonderland.lit' do
81
+ upstream 'localhost', 5347, 'secr3t'
82
+ uploads 'uploads'
83
+ storage 'couchdb' do
84
+ host 'localhost'
85
+ port 5984
86
+ database 'wonderland_lit'
87
+ tls false
88
+ username ''
89
+ password ''
90
+ index_dir '.'
91
+ end
92
+ end
93
+ end
94
+ assert File.exist?('uploads')
95
+ end
96
+
97
+ def test_missing_upstream_raises
98
+ assert_raises(RuntimeError) do
99
+ Vines::Services::Config.new do
100
+ host 'vines.wonderland.lit' do
101
+ storage 'couchdb' do
102
+ host 'localhost'
103
+ port 5984
104
+ database 'wonderland_lit'
105
+ tls false
106
+ username ''
107
+ password ''
108
+ index_dir '.'
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def test_invalid_upstream_raises
116
+ assert_raises(RuntimeError) do
117
+ Vines::Services::Config.new do
118
+ host 'vines.wonderland.lit' do
119
+ upstream 'localhost', 5347, nil
120
+ storage 'couchdb' do
121
+ host 'localhost'
122
+ port 5984
123
+ database 'wonderland_lit'
124
+ tls false
125
+ username ''
126
+ password ''
127
+ index_dir '.'
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ def test_missing_storage_raises
135
+ assert_raises(RuntimeError) do
136
+ Vines::Services::Config.new do
137
+ host 'vines.wonderland.lit' do
138
+ upstream 'localhost', 5347, 'secr3t'
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def test_duplicate_storage_raises
145
+ assert_raises(RuntimeError) do
146
+ Vines::Services::Config.new do
147
+ host 'vines.wonderland.lit' do
148
+ upstream 'localhost', 5347, 'secr3t'
149
+ storage 'couchdb' do
150
+ host 'localhost'
151
+ port 5984
152
+ database 'wonderland_lit'
153
+ tls false
154
+ username ''
155
+ password ''
156
+ index_dir '.'
157
+ end
158
+ storage 'couchdb' do
159
+ host 'localhost'
160
+ port 5984
161
+ database 'wonderland_lit'
162
+ tls false
163
+ username ''
164
+ password ''
165
+ index_dir '.'
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ def test_multiple_domains
173
+ config = Vines::Services::Config.new do
174
+ host 'vines.wonderland.lit' do
175
+ upstream 'localhost', 5347, 'secr3t'
176
+ storage 'couchdb' do
177
+ host 'localhost'
178
+ port 5984
179
+ database 'wonderland_lit'
180
+ tls false
181
+ username ''
182
+ password ''
183
+ index_dir '.'
184
+ end
185
+ end
186
+ host 'vines.verona.lit' do
187
+ upstream 'localhost', 5347, 'secr3t'
188
+ storage 'couchdb' do
189
+ host 'localhost'
190
+ port 5984
191
+ database 'verona_lit'
192
+ tls false
193
+ username ''
194
+ password ''
195
+ index_dir '.'
196
+ end
197
+ end
198
+ end
199
+ assert_equal 2, config.vhosts.size
200
+ assert_equal 'vines.wonderland.lit', config.vhosts['vines.wonderland.lit'].name
201
+ assert_equal 'vines.verona.lit', config.vhosts['vines.verona.lit'].name
202
+ end
203
+
204
+ def test_invalid_log_level
205
+ assert_raises(RuntimeError) do
206
+ config = Vines::Services::Config.new do
207
+ log 'bogus'
208
+ host 'vines.wonderland.lit' do
209
+ upstream 'localhost', 5347, 'secr3t'
210
+ storage 'couchdb' do
211
+ host 'localhost'
212
+ port 5984
213
+ database 'wonderland_lit'
214
+ tls false
215
+ username ''
216
+ password ''
217
+ index_dir '.'
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ def test_valid_log_level
225
+ config = Vines::Services::Config.new do
226
+ log :error
227
+ host 'vines.wonderland.lit' do
228
+ upstream 'localhost', 5347, 'secr3t'
229
+ storage 'couchdb' do
230
+ host 'localhost'
231
+ port 5984
232
+ database 'wonderland_lit'
233
+ tls false
234
+ username ''
235
+ password ''
236
+ index_dir '.'
237
+ end
238
+ end
239
+ end
240
+ assert_equal Logger::ERROR, Class.new.extend(Vines::Log).log.level
241
+ end
242
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines/services'
4
+ require 'minitest/autorun'
5
+
6
+ class PriorityQueueTest < MiniTest::Unit::TestCase
7
+ def test_queue_push_sorts
8
+ queue = Vines::Services::PriorityQueue.new
9
+ nums = (0..10_000).map {|i| rand(10_000) }
10
+ popped = []
11
+ EM.run do
12
+ nums.each {|num| queue.push(num) }
13
+ assert_equal nums.size, queue.size
14
+ (nums.size - 1).times do
15
+ queue.pop {|item| popped << item }
16
+ end
17
+ queue.pop {|item| popped << item; EM.stop }
18
+ end
19
+ assert queue.empty?
20
+ assert_equal 0, queue.size
21
+ assert_equal nums.sort {|a, b| -(a <=> b) }, popped
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines/services'
4
+ require 'minitest/autorun'
5
+
6
+ class CouchDBTest < MiniTest::Unit::TestCase
7
+ def teardown
8
+ FileUtils.rm(Dir.glob('localhost-*.db'))
9
+ end
10
+
11
+ def test_init
12
+ assert_raises(RuntimeError) { Vines::Services::Storage::CouchDB.new {} }
13
+ assert_raises(RuntimeError) { Vines::Services::Storage::CouchDB.new { host 'localhost' } }
14
+ assert_raises(RuntimeError) do
15
+ Vines::Services::Storage::CouchDB.new do
16
+ host 'localhost'
17
+ port '5984'
18
+ database 'test'
19
+ index_dir "./bogus/#{rand(1_000_000)}"
20
+ end
21
+ end
22
+ # shouldn't raise an error
23
+ Vines::Services::Storage::CouchDB.new do
24
+ host 'localhost'
25
+ port '5984'
26
+ database 'test'
27
+ index_dir '.'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines/services'
4
+ require 'minitest/autorun'
5
+
6
+ class CompilerTest < MiniTest::Unit::TestCase
7
+ def setup
8
+ @compiler = Vines::Services::VQL::Compiler.new
9
+ @query = "foo.bar like ' spam ' and (age < 42 or age > 99)"
10
+ end
11
+
12
+ def test_to_js_raises
13
+ assert_raises(ArgumentError) { @compiler.to_js(nil) }
14
+ assert_raises(ArgumentError) { @compiler.to_js(' ') }
15
+ end
16
+
17
+ def test_to_sql_raises
18
+ assert_raises(ArgumentError) { @compiler.to_sql(nil) }
19
+ assert_raises(ArgumentError) { @compiler.to_sql(' ') }
20
+ end
21
+
22
+ def test_to_js
23
+ js = @compiler.to_js(@query)
24
+ expected = %q{
25
+ function(doc) {
26
+ if (doc.type != 'System') return;
27
+ try {
28
+ var match = (doc.ohai.foo.bar.indexOf(' spam ') !== -1) && ((doc.ohai.age < 42) || (doc.ohai.age > 99));
29
+ if (match) {
30
+ var name = doc['_id'].replace('system:', '');
31
+ var os = doc.ohai.kernel.os.toLowerCase().replace('gnu/', '');
32
+ emit(name, os);
33
+ }
34
+ } catch(e) {
35
+ log(e.message);
36
+ }
37
+ }
38
+ }
39
+ assert_equal strip(expected), strip(js)
40
+ end
41
+
42
+ def test_to_full_js
43
+ service = Struct.new(:id, :code)
44
+ services = [service.new(42, 'fqdn is "www"'), service.new(1, 'age > 42')]
45
+ js = @compiler.to_full_js(services)
46
+ expected = %q{
47
+ function(doc) {
48
+ if (doc.type != 'System') return;
49
+ var name = doc['_id'].replace('system:', '');
50
+ var os = doc.ohai.kernel.os.toLowerCase().replace('gnu/', '');
51
+
52
+ try {
53
+ var match = (doc.ohai.fqdn === "www");
54
+ if (match) {
55
+ emit([0, '42'], {name: name, os: os});
56
+ emit([1, name], '42');
57
+ }
58
+ } catch(e) {
59
+ log(e.message);
60
+ }
61
+
62
+ try {
63
+ var match = (doc.ohai.age > 42);
64
+ if (match) {
65
+ emit([0, '1'], {name: name, os: os});
66
+ emit([1, name], '1');
67
+ }
68
+ } catch(e) {
69
+ log(e.message);
70
+ }
71
+
72
+ }
73
+ }
74
+ assert_equal strip(expected), strip(js)
75
+ end
76
+
77
+ def test_to_sql
78
+ sql, params = @compiler.to_sql(@query)
79
+ expected = %q{
80
+ select name, os from systems
81
+ inner join attributes a0 on id=a0.system_id and a0.key=?
82
+ inner join attributes a1 on id=a1.system_id and a1.key=?
83
+ inner join attributes a2 on id=a2.system_id and a2.key=?
84
+ where a0.value like ? and (cast(a1.value as number) < ? or cast(a2.value as number) > ?)
85
+ order by name
86
+ }
87
+ assert_equal strip(expected), strip(sql)
88
+ assert_equal ['foo.bar', 'age', 'age', '% spam %', '42', '99'], params
89
+ end
90
+
91
+ private
92
+
93
+ def strip(str)
94
+ str.strip.gsub(/[ ]{2,}/, '')
95
+ end
96
+ end
@@ -0,0 +1,233 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'vines/services'
4
+ require 'minitest/autorun'
5
+
6
+ class VinesQLTest < MiniTest::Unit::TestCase
7
+ def setup
8
+ Citrus.load(File.expand_path('../../../lib/vines/services/vql/vql.citrus', __FILE__))
9
+ end
10
+
11
+ def test_negative_number
12
+ match = VinesQL.parse('foo < -42 ')
13
+ assert_equal '(doc.ohai.foo < -42)', match.js
14
+ assert_equal 'cast(value as number) < ?', match.sql
15
+ assert_equal ['foo', '-42'], match.params
16
+
17
+ match = VinesQL.parse('foo < -0.5 ')
18
+ assert_equal '(doc.ohai.foo < -0.5)', match.js
19
+ assert_equal 'cast(value as number) < ?', match.sql
20
+ assert_equal ['foo', '-0.5'], match.params
21
+
22
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 < -') }
23
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 < --1') }
24
+ end
25
+
26
+ def test_less_than
27
+ match = VinesQL.parse('foo < 1 ')
28
+ assert_equal '(doc.ohai.foo < 1)', match.js
29
+ assert_equal 'cast(value as number) < ?', match.sql
30
+ assert_equal ['foo', '1'], match.params
31
+
32
+ match = VinesQL.parse('foo<=1 ')
33
+ assert_equal '(doc.ohai.foo <= 1)', match.js
34
+ assert_equal 'cast(value as number) <= ?', match.sql
35
+ assert_equal ['foo', '1'], match.params
36
+
37
+ match = VinesQL.parse('foo < 1.5 ')
38
+ assert_equal '(doc.ohai.foo < 1.5)', match.js
39
+ assert_equal 'cast(value as number) < ?', match.sql
40
+ assert_equal ['foo', '1.5'], match.params
41
+
42
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 < 42') }
43
+ end
44
+
45
+ def test_greater_than
46
+ match = VinesQL.parse('foo > 1')
47
+ assert_equal '(doc.ohai.foo > 1)', match.js
48
+ assert_equal 'cast(value as number) > ?', match.sql
49
+ assert_equal ['foo', '1'], match.params
50
+
51
+ match = VinesQL.parse('foo>=1')
52
+ assert_equal '(doc.ohai.foo >= 1)', match.js
53
+ assert_equal 'cast(value as number) >= ?', match.sql
54
+ assert_equal ['foo', '1'], match.params
55
+
56
+ match = VinesQL.parse('foo > 1.5')
57
+ assert_equal '(doc.ohai.foo > 1.5)', match.js
58
+ assert_equal 'cast(value as number) > ?', match.sql
59
+ assert_equal ['foo', '1.5'], match.params
60
+
61
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 > 42') }
62
+ end
63
+
64
+ def test_is
65
+ match = VinesQL.parse('foo.bar is 13')
66
+ assert_equal '(doc.ohai.foo.bar === 13)', match.js
67
+ assert_equal 'value=?', match.sql
68
+ assert_equal ['foo.bar', '13'], match.params
69
+
70
+ match = VinesQL.parse('foo.bar is 13.5')
71
+ assert_equal '(doc.ohai.foo.bar === 13.5)', match.js
72
+ assert_equal 'value=?', match.sql
73
+ assert_equal ['foo.bar', '13.5'], match.params
74
+
75
+ match = VinesQL.parse('foo.bar is true')
76
+ assert_equal '(doc.ohai.foo.bar === true)', match.js
77
+ assert_equal 'value=?', match.sql
78
+ assert_equal ['foo.bar', 'true'], match.params
79
+
80
+ match = VinesQL.parse('foo.bar is false')
81
+ assert_equal '(doc.ohai.foo.bar === false)', match.js
82
+ assert_equal 'value=?', match.sql
83
+ assert_equal ['foo.bar', 'false'], match.params
84
+
85
+ match = VinesQL.parse('foo.bar is null')
86
+ assert_equal '(doc.ohai.foo.bar === null)', match.js
87
+ assert_equal 'value is null', match.sql
88
+ assert_equal ['foo.bar'], match.params
89
+
90
+ match = VinesQL.parse('foo.bar is " bar "')
91
+ assert_equal '(doc.ohai.foo.bar === " bar ")', match.js
92
+ assert_equal 'value=?', match.sql
93
+ assert_equal ['foo.bar', ' bar '], match.params
94
+
95
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 is 42') }
96
+ end
97
+
98
+ def test_is_not
99
+ match = VinesQL.parse('foo.bar is not 13')
100
+ assert_equal '(doc.ohai.foo.bar !== 13)', match.js
101
+ assert_equal 'value <> ?', match.sql
102
+ assert_equal ['foo.bar', '13'], match.params
103
+
104
+ match = VinesQL.parse('foo.bar is not 13.5')
105
+ assert_equal '(doc.ohai.foo.bar !== 13.5)', match.js
106
+ assert_equal 'value <> ?', match.sql
107
+ assert_equal ['foo.bar', '13.5'], match.params
108
+
109
+ match = VinesQL.parse('foo.bar is not true')
110
+ assert_equal '(doc.ohai.foo.bar !== true)', match.js
111
+ assert_equal 'value <> ?', match.sql
112
+ assert_equal ['foo.bar', 'true'], match.params
113
+
114
+ match = VinesQL.parse('foo.bar is not false')
115
+ assert_equal '(doc.ohai.foo.bar !== false)', match.js
116
+ assert_equal 'value <> ?', match.sql
117
+ assert_equal ['foo.bar', 'false'], match.params
118
+
119
+ match = VinesQL.parse('foo.bar is not null')
120
+ assert_equal '(doc.ohai.foo.bar !== null)', match.js
121
+ assert_equal 'value is not null', match.sql
122
+ assert_equal ['foo.bar'], match.params
123
+
124
+ match = VinesQL.parse('foo.bar is not " bar "')
125
+ assert_equal '(doc.ohai.foo.bar !== " bar ")', match.js
126
+ assert_equal 'value <> ?', match.sql
127
+ assert_equal ['foo.bar', ' bar '], match.params
128
+
129
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 is not 42') }
130
+ end
131
+
132
+ def test_like
133
+ match = VinesQL.parse('foo.bar like " bar "')
134
+ assert_equal '(doc.ohai.foo.bar.indexOf(" bar ") !== -1)', match.js
135
+ assert_equal 'value like ?', match.sql
136
+ assert_equal ['foo.bar', '% bar %'], match.params
137
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 like 42') }
138
+ assert_raises(Citrus::ParseError) { VinesQL.parse('foo.bar like 42') }
139
+ end
140
+
141
+ def test_not_like
142
+ match = VinesQL.parse('foo.bar not like " bar "')
143
+ assert_equal '(doc.ohai.foo.bar.indexOf(" bar ") === -1)', match.js
144
+ assert_equal 'value not like ?', match.sql
145
+ assert_equal ['foo.bar', '% bar %'], match.params
146
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 not like 42') }
147
+ assert_raises(Citrus::ParseError) { VinesQL.parse('foo.bar not like 42') }
148
+ end
149
+
150
+ def test_starts_with
151
+ match = VinesQL.parse('foo.bar starts with " bar "')
152
+ assert_equal '(doc.ohai.foo.bar.lastIndexOf(" bar ", 0) === 0)', match.js
153
+ assert_equal 'value like ?', match.sql
154
+ assert_equal ['foo.bar', ' bar %'], match.params
155
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 starts with 42') }
156
+ assert_raises(Citrus::ParseError) { VinesQL.parse('foo.bar starts with 42') }
157
+ end
158
+
159
+ def test_ends_with
160
+ match = VinesQL.parse('foo.bar ends with " bar "')
161
+ assert_equal '(doc.ohai.foo.bar.match(" bar " + \'$\'))', match.js
162
+ assert_equal 'value like ?', match.sql
163
+ assert_equal ['foo.bar', '% bar '], match.params
164
+ assert_raises(Citrus::ParseError) { VinesQL.parse('12 ends with 42') }
165
+ assert_raises(Citrus::ParseError) { VinesQL.parse('foo.bar ends with 42') }
166
+ end
167
+
168
+ def test_group
169
+ match = VinesQL.parse('(foo is 12 or bar is 42) and spam > 0')
170
+ assert_equal '((doc.ohai.foo === 12) || (doc.ohai.bar === 42)) && (doc.ohai.spam > 0)', match.js
171
+ assert_equal '(value=? or value=?) and cast(value as number) > ?', match.sql
172
+ assert_equal ['foo', '12', 'bar', '42', 'spam', '0'], match.params
173
+ end
174
+
175
+ def test_complex_syntax_with_extra_whitespace
176
+ syntax = %q{
177
+ name like 'abc' and
178
+ (
179
+ address.city is 'LA' or
180
+ address.city is not 'NYC'
181
+ ) or
182
+ ssn not like '123'
183
+ and status starts with "valid"
184
+ and status ends with " test "
185
+ or ( age > 42 )
186
+ }.strip
187
+
188
+ js =
189
+ "(doc.ohai.name.indexOf('abc') !== -1) && " +
190
+ "((doc.ohai.address.city === 'LA') || (doc.ohai.address.city !== 'NYC')) || " +
191
+ "(doc.ohai.ssn.indexOf('123') === -1) && " +
192
+ "(doc.ohai.status.lastIndexOf(\"valid\", 0) === 0) && " +
193
+ "(doc.ohai.status.match(\" test \" + '$')) || " +
194
+ "((doc.ohai.age > 42))"
195
+
196
+ sql =
197
+ "value like ? and " +
198
+ "(value=? or value <> ?) or " +
199
+ "value not like ? and " +
200
+ "value like ? and " +
201
+ "value like ? or " +
202
+ "(cast(value as number) > ?)"
203
+
204
+ params = [
205
+ 'name', '%abc%',
206
+ 'address.city', 'LA',
207
+ 'address.city', 'NYC',
208
+ 'ssn', '%123%',
209
+ 'status', 'valid%',
210
+ 'status', '% test ',
211
+ 'age', '42']
212
+
213
+ match = VinesQL.parse(syntax)
214
+ assert_equal js, match.js
215
+ assert_equal sql, match.sql
216
+ assert_equal params, match.params
217
+ end
218
+
219
+ def test_invalid_syntax
220
+ assert_raises(Citrus::ParseError) do
221
+ VinesQL.parse('some bogus syntax!')
222
+ end
223
+ end
224
+
225
+ def test_params_are_strings_not_matches
226
+ match = VinesQL.parse('foo < 42 and bar is "foo" and spam is not true')
227
+ assert_equal ['foo', '42', 'bar', 'foo', 'spam', 'true'], match.params
228
+ # were incorrectly returned as Citrus::Match objects
229
+ match.params.each do |p|
230
+ assert_equal String, p.class
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,51 @@
1
+ class Api
2
+ constructor: (@session) ->
3
+
4
+ jid: -> "vines.#{@session.bareJid().split('@')[1]}"
5
+
6
+ get: (ns, criteria, callback) ->
7
+ node = @session.xml """
8
+ <iq id="#{@session.uniqueId()}" to="#{this.jid()}" type="get">
9
+ <query xmlns="#{ns}"/>
10
+ </iq>
11
+ """
12
+ $('query', node).attr key, value for key, value of criteria
13
+
14
+ @session.sendIQ node, (result) =>
15
+ ok = $(result).attr('type') == 'result'
16
+ return unless ok
17
+ callback JSON.parse $('query', result).text()
18
+
19
+ get2: (ns, body, callback) ->
20
+ node = @session.xml """
21
+ <iq id="#{@session.uniqueId()}" to="#{this.jid()}" type="get">
22
+ <query xmlns="#{ns}"/>
23
+ </iq>
24
+ """
25
+ $('query', node).text body
26
+ @session.sendIQ node, (result) =>
27
+ ok = $(result).attr('type') == 'result'
28
+ return unless ok
29
+ callback JSON.parse $('query', result).text()
30
+
31
+ remove: (ns, id, callback) ->
32
+ node = @session.xml """
33
+ <iq id="#{@session.uniqueId()}" to="#{this.jid()}" type="set">
34
+ <query xmlns="#{ns}" action="delete" id=""/>
35
+ </iq>
36
+ """
37
+ $('query', node).attr 'id', id
38
+ @session.sendIQ node, callback
39
+
40
+ save: (ns, obj, callback) ->
41
+ node = @session.xml """
42
+ <iq id="#{@session.uniqueId()}" to="#{this.jid()}" type="set">
43
+ <query xmlns="#{ns}"/>
44
+ </iq>
45
+ """
46
+ $('query', node).text JSON.stringify obj
47
+
48
+ @session.sendIQ node, (result) =>
49
+ ok = $(result).attr('type') == 'result'
50
+ return unless ok
51
+ callback JSON.parse $('query', result).text()
@@ -0,0 +1,18 @@
1
+ class Commands
2
+ constructor: ->
3
+ @buf = []
4
+ @index = 0
5
+
6
+ prev: ->
7
+ val = @buf[--@index]
8
+ @index = -1 unless val
9
+ val || ''
10
+
11
+ next: ->
12
+ val = @buf[++@index]
13
+ @index = @buf.length unless val
14
+ val || ''
15
+
16
+ push: (cmd) ->
17
+ @buf.push cmd
18
+ @index = @buf.length