reflexion 0.1.1

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 (74) hide show
  1. data/ChangeLog +8 -0
  2. data/README +4 -0
  3. data/Rakefile +70 -0
  4. data/VERSION +1 -0
  5. data/examples/hello/Rakefile +41 -0
  6. data/examples/hello/main.cpp +18 -0
  7. data/examples/ruby/app.rb +13 -0
  8. data/examples/ruby/checker.rb +41 -0
  9. data/examples/ruby/fps.rb +49 -0
  10. data/examples/ruby/hello.rb +38 -0
  11. data/examples/ruby/key.rb +44 -0
  12. data/examples/ruby/shapes.rb +124 -0
  13. data/examples/ruby/text.rb +35 -0
  14. data/ext/reflex/application.cpp +151 -0
  15. data/ext/reflex/extconf.rb +57 -0
  16. data/ext/reflex/key.cpp +128 -0
  17. data/ext/reflex/native.cpp +22 -0
  18. data/ext/reflex/points.cpp +160 -0
  19. data/ext/reflex/reflex.cpp +83 -0
  20. data/ext/reflex/reflex.h +39 -0
  21. data/ext/reflex/window.cpp +357 -0
  22. data/include/reflex/application.h +50 -0
  23. data/include/reflex/defs.h +258 -0
  24. data/include/reflex/helpers.h +32 -0
  25. data/include/reflex/reflex.h +26 -0
  26. data/include/reflex/ruby/application.h +39 -0
  27. data/include/reflex/ruby/key.h +39 -0
  28. data/include/reflex/ruby/points.h +39 -0
  29. data/include/reflex/ruby/reflex.h +21 -0
  30. data/include/reflex/ruby/window.h +39 -0
  31. data/include/reflex/ruby.h +14 -0
  32. data/include/reflex/window.h +79 -0
  33. data/include/reflex.h +13 -0
  34. data/lib/reflex/application.rb +25 -0
  35. data/lib/reflex/autoinit.rb +11 -0
  36. data/lib/reflex/bounds.rb +133 -0
  37. data/lib/reflex/helpers.rb +52 -0
  38. data/lib/reflex/module.rb +30 -0
  39. data/lib/reflex/point.rb +69 -0
  40. data/lib/reflex/reflex.rb +21 -0
  41. data/lib/reflex/window.rb +65 -0
  42. data/lib/reflex.rb +11 -0
  43. data/reflex.gemspec +57 -0
  44. data/src/cocoa/application.mm +90 -0
  45. data/src/cocoa/applicationdata.h +51 -0
  46. data/src/cocoa/cocoaapplication.h +19 -0
  47. data/src/cocoa/cocoaapplication.mm +180 -0
  48. data/src/cocoa/cocoawindow.h +44 -0
  49. data/src/cocoa/cocoawindow.mm +171 -0
  50. data/src/cocoa/defs.h +34 -0
  51. data/src/cocoa/defs.mm +84 -0
  52. data/src/cocoa/openglview.h +17 -0
  53. data/src/cocoa/openglview.mm +186 -0
  54. data/src/cocoa/reflex.mm +68 -0
  55. data/src/cocoa/window.mm +118 -0
  56. data/src/cocoa/windowdata.h +51 -0
  57. data/src/defs.cpp +47 -0
  58. data/src/win32/defs.cpp +150 -0
  59. data/src/win32/opengl.cpp +95 -0
  60. data/src/win32/opengl.h +50 -0
  61. data/src/win32/reflex.cpp +65 -0
  62. data/src/win32/window.cpp +480 -0
  63. data/src/window.cpp +60 -0
  64. data/support.rb +56 -0
  65. data/task/ext.rake +42 -0
  66. data/task/gem.rake +33 -0
  67. data/task/git.rake +22 -0
  68. data/task/lib.rake +54 -0
  69. data/test/helpers.rb +15 -0
  70. data/test/test_bounds.rb +163 -0
  71. data/test/test_point.rb +81 -0
  72. data/test/test_reflex.rb +17 -0
  73. data/test/test_window.rb +39 -0
  74. metadata +173 -0
@@ -0,0 +1,480 @@
1
+ #include "reflex/window.h"
2
+
3
+
4
+ #include <windows.h>
5
+ #include <boost/shared_ptr.hpp>
6
+ #include <boost/scoped_array.hpp>
7
+ #include "reflex/reflex.h"
8
+ #include "opengl.h"
9
+
10
+
11
+ namespace Reflex
12
+ {
13
+
14
+
15
+ struct Window::Data
16
+ {
17
+
18
+ Window* this_;
19
+
20
+ HWND hwnd;
21
+
22
+ int hidecount;
23
+
24
+ OpenGL opengl;
25
+
26
+ Data ()
27
+ : this_(NULL), hwnd(NULL), hidecount(1)
28
+ {
29
+ }
30
+
31
+ };// Window::Data
32
+
33
+ typedef boost::shared_ptr<Window::Data> WindowData;
34
+
35
+
36
+ static const char* WINDOWCLASS = "Reflex:WindowClass";
37
+
38
+ static const char* USERDATA_PROP = "Reflex:Window:HWND";
39
+
40
+ enum {UPDATE_TIMER_ID = 99999};
41
+
42
+
43
+ static LRESULT CALLBACK wndproc (HWND, UINT, WPARAM, LPARAM);
44
+
45
+
46
+ static bool
47
+ window_has_wndproc (HWND hwnd)
48
+ {
49
+ if (!hwnd) return false;
50
+
51
+ WNDPROC proc = (WNDPROC) GetWindowLongPtr(hwnd, GWLP_WNDPROC);
52
+ if (proc == wndproc)
53
+ return true;
54
+
55
+ // OpenGL SetPixelFormat() changes GWLP_WNDPROC, so checking
56
+ // it can not determine the hwnd is mine or not.
57
+
58
+ enum {BUFSIZE = 256};
59
+ char buf[BUFSIZE + 1];
60
+ if (
61
+ GetClassName(hwnd, &buf[0], BUFSIZE) == 0 &&
62
+ GetLastError() != 0)
63
+ {
64
+ return false;
65
+ }
66
+
67
+ return stricmp(buf, WINDOWCLASS) == 0;
68
+ }
69
+
70
+ static Window*
71
+ get_window_from_hwnd (HWND hwnd)
72
+ {
73
+ WindowData* data = NULL;
74
+ if (window_has_wndproc(hwnd))
75
+ data = (WindowData*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
76
+ else
77
+ data = (WindowData*) GetProp(hwnd, USERDATA_PROP);
78
+
79
+ return data ? (*data)->this_ : NULL;
80
+ }
81
+
82
+ static bool
83
+ setup_window (Window* win, HWND hwnd)
84
+ {
85
+ if (!win || *win) return false;
86
+
87
+ WindowData* data = new WindowData(win->self);
88
+
89
+ bool ret = false;
90
+ if (window_has_wndproc(hwnd))
91
+ {
92
+ SetLastError(0);
93
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) data);
94
+ ret = GetLastError() == 0;
95
+ }
96
+ else
97
+ ret = SetProp(hwnd, USERDATA_PROP, (HANDLE) data);
98
+
99
+ if (ret)
100
+ {
101
+ win->self->hwnd = hwnd;
102
+ win->self->opengl.init(hwnd);
103
+ }
104
+ else
105
+ delete data;
106
+
107
+ return ret;
108
+ }
109
+
110
+ static bool
111
+ cleanup_window (Window* win)
112
+ {
113
+ if (!win || !*win) return false;
114
+
115
+ HWND hwnd = win->self->hwnd;
116
+ WindowData* data = NULL;
117
+
118
+ bool has_wndproc = window_has_wndproc(hwnd);
119
+ if (has_wndproc)
120
+ data = (WindowData*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
121
+ else
122
+ data = (WindowData*) GetProp(hwnd, USERDATA_PROP);
123
+
124
+ if (!data || data->get() != win->self.get())
125
+ return false;
126
+
127
+ bool ret = false;
128
+ if (has_wndproc)
129
+ {
130
+ SetLastError(0);
131
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
132
+ ret = GetLastError() == 0;
133
+ }
134
+ else
135
+ ret = RemoveProp(hwnd, USERDATA_PROP) != NULL;
136
+
137
+ if (ret)
138
+ {
139
+ win->self->opengl.fin();
140
+ win->self->hwnd = NULL;
141
+ delete data;
142
+ }
143
+
144
+ return ret;
145
+ }
146
+
147
+ static LRESULT CALLBACK
148
+ window_proc (Window* win, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
149
+ {
150
+ if (!win || !*win || win->self->hwnd != hwnd)
151
+ return DefWindowProc(hwnd, msg, wp, lp);
152
+
153
+ switch (msg)
154
+ {
155
+ case WM_CLOSE:
156
+ {
157
+ win->close();
158
+ return 0;
159
+ }
160
+
161
+ case WM_PAINT:
162
+ {
163
+ PAINTSTRUCT ps;
164
+ BeginPaint(hwnd, &ps);
165
+
166
+ if (win->self->opengl.make_current())
167
+ {
168
+ win->draw();
169
+ win->self->opengl.swap_buffers();
170
+ }
171
+
172
+ EndPaint(hwnd, &ps);
173
+ return 0;
174
+ }
175
+
176
+ case WM_MOVE:
177
+ {
178
+ coord l = 0, t = 0;
179
+ win->get_bounds(&l, &t);
180
+ win->moved(l, t);
181
+ break;
182
+ }
183
+
184
+ case WM_SIZE:
185
+ {
186
+ coord l = 0, t = 0, r = 0, b = 0;
187
+ win->get_bounds(&l, &t, &r, &b);
188
+ win->resized(r - l, b - t);
189
+ break;
190
+ }
191
+
192
+ case WM_ERASEBKGND:
193
+ {
194
+ // do nothing.
195
+ return 0;
196
+ }
197
+
198
+ case WM_TIMER:
199
+ {
200
+ if (wp == UPDATE_TIMER_ID)
201
+ win->update();
202
+ return 0;
203
+ }
204
+
205
+ case WM_SYSCOMMAND:
206
+ {
207
+ #if 0
208
+ if (wp == SC_SCREENSAVE || wp == SC_MONITORPOWER)
209
+ return 0;
210
+ #endif
211
+ break;
212
+ }
213
+ }
214
+
215
+ return DefWindowProc(hwnd, msg, wp, lp);
216
+ }
217
+
218
+ static LRESULT CALLBACK
219
+ wndproc (HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
220
+ {
221
+ static int window_total = 0;
222
+
223
+ Window* win = NULL;
224
+ if (msg == WM_NCCREATE)
225
+ {
226
+ CREATESTRUCT* cs = (CREATESTRUCT*) lp;
227
+ win = (Window*) cs->lpCreateParams;
228
+ setup_window(win, hwnd);
229
+
230
+ ++window_total;
231
+ }
232
+
233
+ if (!win)
234
+ win = get_window_from_hwnd(hwnd);
235
+
236
+ LRESULT ret = window_proc(win, hwnd, msg, wp, lp);
237
+
238
+ if (msg == WM_NCDESTROY)
239
+ {
240
+ cleanup_window(win);
241
+
242
+ if (--window_total == 0)
243
+ Reflex::quit();
244
+ }
245
+
246
+ return ret;
247
+ }
248
+
249
+ static bool
250
+ register_windowclass ()
251
+ {
252
+ static bool registered = false;
253
+ if (registered) return true;
254
+
255
+ WNDCLASSEX wc;
256
+ memset(&wc, 0, sizeof(wc));
257
+
258
+ wc.cbSize = sizeof(wc);
259
+ wc.lpszClassName = WINDOWCLASS;
260
+ wc.lpfnWndProc = (WNDPROC) wndproc;
261
+ wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
262
+ wc.hInstance = GetModuleHandle(NULL);
263
+ //wc.hIcon = LoadIcon(wc.hInstance, IDI_APP_LARGE);
264
+ //wc.hIconSm = LoadIcon(wc.hInstance, IDI_APP_SMALL);
265
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
266
+ wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
267
+ wc.lpszMenuName = NULL;
268
+ wc.cbWndExtra = sizeof(Window*);
269
+
270
+ if (!RegisterClassEx(&wc))
271
+ return false;
272
+
273
+ return registered = true;
274
+ }
275
+
276
+ static bool
277
+ create_window (Window* win)
278
+ {
279
+ if (!win) return false;
280
+
281
+ if (!register_windowclass())
282
+ return false;
283
+
284
+ CreateWindowEx(
285
+ 0, WINDOWCLASS, "",
286
+ WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
287
+ 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), win);
288
+ return *win;
289
+ }
290
+
291
+
292
+ Window::Window ()
293
+ {
294
+ self->this_ = this;
295
+
296
+ create_window(this);
297
+ }
298
+
299
+ Window::~Window ()
300
+ {
301
+ close();
302
+ self->this_ = NULL;
303
+ }
304
+
305
+ bool
306
+ Window::close ()
307
+ {
308
+ if (!self->hwnd || !IsWindow(self->hwnd))
309
+ return false;
310
+
311
+ return DestroyWindow(self->hwnd) != FALSE;
312
+ }
313
+
314
+ static bool
315
+ start_timer (HWND hwnd, UINT id, UINT interval)
316
+ {
317
+ if (!hwnd) return false;
318
+
319
+ return SetTimer(hwnd, id, interval, NULL) != 0;
320
+ }
321
+
322
+ static bool
323
+ stop_timer (HWND hwnd, UINT id)
324
+ {
325
+ if (!hwnd || id == 0) return false;
326
+
327
+ return KillTimer(hwnd, id) != FALSE;
328
+ }
329
+
330
+ bool
331
+ Window::show ()
332
+ {
333
+ if (!*this) return false;
334
+
335
+ if (--self->hidecount != 0) return true;
336
+
337
+ SetLastError(0);
338
+ ShowWindow(self->hwnd, SW_SHOW);
339
+ if (GetLastError() != 0) return false;
340
+
341
+ if (!UpdateWindow(self->hwnd))
342
+ return false;
343
+
344
+ start_timer(self->hwnd, UPDATE_TIMER_ID, 1000 / 60);
345
+ return true;
346
+ }
347
+
348
+ bool
349
+ Window::hide ()
350
+ {
351
+ if (!*this) return false;
352
+
353
+ if (++self->hidecount != 1) return true;
354
+
355
+ SetLastError(0);
356
+ ShowWindow(self->hwnd, SW_HIDE);
357
+ if (GetLastError() != 0) return false;
358
+
359
+ stop_timer(self->hwnd, UPDATE_TIMER_ID);
360
+ return true;
361
+ }
362
+
363
+ bool
364
+ Window::hidden () const
365
+ {
366
+ if (!*this) return true;
367
+
368
+ return self->hidecount > 0;
369
+ }
370
+
371
+ void
372
+ Window::update ()
373
+ {
374
+ }
375
+
376
+ void
377
+ Window::draw ()
378
+ {
379
+ }
380
+
381
+ bool
382
+ Window::redraw ()
383
+ {
384
+ if (!*this) return false;
385
+
386
+ #if 1
387
+ InvalidateRect(self->hwnd, NULL, FALSE);
388
+ return true;
389
+ #else
390
+ return FALSE != RedrawWindow(
391
+ self->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | EDW_ALLCHILDREN);
392
+ #endif
393
+ }
394
+
395
+ bool
396
+ Window::get_title (String* title) const
397
+ {
398
+ if (!*this || !title) return false;
399
+
400
+ *title = "";
401
+
402
+ int size = GetWindowTextLength(self->hwnd);
403
+ if (size <= 0) return true;
404
+
405
+ boost::scoped_array<char> buf(new char[size + 1]);
406
+ if (GetWindowText(self->hwnd, &buf[0], size + 1) != size)
407
+ return false;
408
+
409
+ *title = &buf[0];
410
+ return true;
411
+ }
412
+
413
+ bool
414
+ Window::set_title (const char* title)
415
+ {
416
+ if (!*this || !title) return false;
417
+
418
+ return SetWindowText(self->hwnd, title) != FALSE;
419
+ }
420
+
421
+ bool
422
+ Window::get_bounds (coord* x, coord* y, coord* width, coord* height)
423
+ {
424
+ if (!*this || (!x && !y && !width && !height))
425
+ return false;
426
+
427
+ RECT rect;
428
+ if (!GetWindowRect(self->hwnd, &rect))
429
+ return false;
430
+
431
+ if (x) *x = rect.left;
432
+ if (y) *y = rect.top;
433
+ if (width) *width = rect.right - rect.left + 1;
434
+ if (height) *height = rect.bottom - rect.top + 1;
435
+ return true;
436
+ }
437
+
438
+ bool
439
+ Window::set_bounds (coord x, coord y, coord width, coord height)
440
+ {
441
+ if (!*this) return false;
442
+
443
+ coord xx, yy, ww, hh;
444
+ if (!get_bounds(&xx, &yy, &ww, &hh))
445
+ return false;
446
+
447
+ UINT flags = 0;
448
+ if (x == xx && y == yy) flags |= SWP_NOMOVE;
449
+ if (width == ww && height == hh) flags |= SWP_NOSIZE;
450
+
451
+ if (flags == (SWP_NOMOVE | SWP_NOSIZE)) return true;
452
+
453
+ return FALSE != SetWindowPos(
454
+ self->hwnd, NULL, (int) x, (int) y, (int) width, (int) height,
455
+ flags | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
456
+ }
457
+
458
+ void
459
+ Window::moved (coord x, coord y)
460
+ {
461
+ }
462
+
463
+ void
464
+ Window::resized (coord width, coord height)
465
+ {
466
+ }
467
+
468
+ Window::operator bool () const
469
+ {
470
+ return self && self->this_ && self->hwnd && IsWindow(self->hwnd);
471
+ }
472
+
473
+ bool
474
+ Window::operator ! () const
475
+ {
476
+ return !operator bool();
477
+ }
478
+
479
+
480
+ }// Reflex
data/src/window.cpp ADDED
@@ -0,0 +1,60 @@
1
+ #include "reflex/window.h"
2
+
3
+
4
+ namespace Reflex
5
+ {
6
+
7
+
8
+ void
9
+ Window::update ()
10
+ {
11
+ }
12
+
13
+ void
14
+ Window::draw ()
15
+ {
16
+ }
17
+
18
+ void
19
+ Window::moved (coord x, coord y)
20
+ {
21
+ }
22
+
23
+ void
24
+ Window::resized (coord width, coord height)
25
+ {
26
+ }
27
+
28
+ void
29
+ Window::key_down (const Key& key)
30
+ {
31
+ }
32
+
33
+ void
34
+ Window::key_up (const Key& key)
35
+ {
36
+ }
37
+
38
+ void
39
+ Window::points_down (const Points& points)
40
+ {
41
+ }
42
+
43
+ void
44
+ Window::points_up (const Points& points)
45
+ {
46
+ }
47
+
48
+ void
49
+ Window::points_moved (const Points& points)
50
+ {
51
+ }
52
+
53
+ bool
54
+ Window::operator ! () const
55
+ {
56
+ return !operator bool();
57
+ }
58
+
59
+
60
+ }// Reflex
data/support.rb ADDED
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'erb'
3
+ require 'pp'
4
+
5
+
6
+ def version ()
7
+ open("VERSION") {|f| f.readline.chomp}
8
+ end
9
+
10
+ def glob (*patterns)
11
+ paths = []
12
+ patterns.each do |pattern|
13
+ paths.concat Dir.glob(pattern)
14
+ end
15
+ paths
16
+ end
17
+
18
+ def erb (str)
19
+ ERB.new(str, nil, "%").result binding
20
+ end
21
+
22
+ def compile (path, out)
23
+ open(path) do |input|
24
+ open(out, "w") do |output|
25
+ output.write erb(input.read)
26
+ end
27
+ end
28
+ #rescue
29
+ end
30
+
31
+ def convertions (paths, convs)
32
+ raise "empty conversion." if convs.empty?
33
+ paths = paths.map do |path|
34
+ convpath = path
35
+ convs.each do |from, to|
36
+ convpath = convpath.sub(/#{from.gsub('.', '\.')}$/, to)
37
+ end
38
+ [path, convpath]
39
+ end
40
+ Hash[*paths.flatten]
41
+ end
42
+
43
+ alias sh_original sh
44
+
45
+ def sh (*args)
46
+ sh_original *args
47
+ #rescue
48
+ end
49
+
50
+ def win32? ()
51
+ RUBY_PLATFORM =~ /mswin|ming|cygwin/
52
+ end
53
+
54
+ def cocoa? ()
55
+ RUBY_PLATFORM =~ /darwin/
56
+ end
data/task/ext.rake ADDED
@@ -0,0 +1,42 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+
4
+ require 'rucy/module'
5
+ require 'rays/module'
6
+
7
+
8
+ namespace :ext do
9
+
10
+ dir = "#{EXTDIR}/#{NAME}"
11
+ name = "#{NAME}/native"
12
+ outname = "#{name}.#{EXTEXT}"
13
+ out = File.join EXTDIR, outname
14
+
15
+ extconf = File.join dir, "extconf.rb"
16
+ makefile = File.join dir, "Makefile"
17
+ depend = File.join dir, "depend"
18
+
19
+ cpps = Dir.glob("#{dir}/**/*.cpp")
20
+
21
+ task :build => makefile do
22
+ sh %( cd #{dir} && #{MAKE} )
23
+ end
24
+
25
+ task :clean do
26
+ sh %( cd #{dir} && #{MAKE} clean ) if File.exist? makefile
27
+ sh %( rm -f #{makefile} #{depend} )
28
+ end
29
+
30
+ file makefile => [extconf, depend] do
31
+ sh %( cd #{dir} && #{RUBY} #{File.basename extconf} )
32
+ end
33
+
34
+ file depend => ["lib:build"] + cpps do
35
+ incdirs = INCDIRS + Rucy.include_dirs + Rays.include_dirs;
36
+ incdirs = incdirs.map{|s| " -I#{s}"}.join
37
+ srcs = cpps.map{|cpp| File.basename cpp}.join ' '
38
+ dep = File.basename depend
39
+ sh %( cd #{dir} && #{CC} -M #{CFLAGS} #{incdirs} #{srcs} > #{dep} )
40
+ end
41
+
42
+ end# :ext
data/task/gem.rake ADDED
@@ -0,0 +1,33 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+
4
+ namespace :gem do
5
+
6
+ name = "#{NAME}ion"
7
+
8
+ gemspec = "#{NAME}.gemspec"
9
+ gem = "#{name}-#{version}.gem"
10
+
11
+ task :build => gem
12
+
13
+ task :install => gem do
14
+ sh %( #{GEM} install #{gem} )
15
+ end
16
+
17
+ task :uninstall do
18
+ sh %( #{GEM} uninstall #{name} )
19
+ end
20
+
21
+ task :clean do
22
+ sh %( rm -f #{gem} )
23
+ end
24
+
25
+ task :upload => gem do
26
+ sh %( #{GEM} push #{gem} )
27
+ end
28
+
29
+ file gem => "lib:build" do
30
+ sh %( #{GEM} build #{gemspec} )
31
+ end
32
+
33
+ end# :gem
data/task/git.rake ADDED
@@ -0,0 +1,22 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+
4
+ namespace :git do
5
+
6
+ task :status do
7
+ sh %( #{GIT} status )
8
+ end
9
+
10
+ task :diff do
11
+ sh %( #{GIT} diff | cat )
12
+ end
13
+
14
+ task :push do
15
+ sh %( #{GIT} push )
16
+ end
17
+
18
+ task :pull do
19
+ sh %( #{GIT} pull )
20
+ end
21
+
22
+ end# :git