showoff 0.7.0 → 0.9.7

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +53 -475
  3. data/Rakefile +17 -18
  4. data/bin/showoff +29 -7
  5. data/lib/commandline_parser.rb +1 -1
  6. data/lib/showoff/version.rb +3 -0
  7. data/lib/showoff.rb +600 -91
  8. data/lib/showoff_utils.rb +110 -4
  9. data/public/css/disconnected-large.png +0 -0
  10. data/public/css/disconnected.png +0 -0
  11. data/public/css/fast.png +0 -0
  12. data/public/css/grippy-close.png +0 -0
  13. data/public/css/grippy.png +0 -0
  14. data/public/css/onepage.css +6 -0
  15. data/public/css/pace.png +0 -0
  16. data/public/css/paceMarker.png +0 -0
  17. data/public/css/presenter.css +333 -43
  18. data/public/css/sh_style.css +15 -0
  19. data/public/css/showoff.css +373 -48
  20. data/public/css/slow.png +0 -0
  21. data/public/css/spinner.gif +0 -0
  22. data/public/css/tipsy.css +26 -0
  23. data/public/favicon.ico +0 -0
  24. data/public/js/jquery.parsequery.min.js +2 -0
  25. data/public/js/jquery.tipsy.js +260 -0
  26. data/public/js/onepage.js +2 -3
  27. data/public/js/presenter.js +384 -33
  28. data/public/js/sh_lang/sh_gherkin.js +112 -0
  29. data/public/js/sh_lang/sh_gherkin.min.js +1 -0
  30. data/public/js/sh_lang/sh_ini.js +87 -0
  31. data/public/js/sh_lang/sh_ini.min.js +87 -0
  32. data/public/js/sh_lang/sh_puppet.js +182 -0
  33. data/public/js/sh_lang/sh_puppet.min.js +182 -0
  34. data/public/js/sh_lang/sh_puppet_output.js +22 -0
  35. data/public/js/sh_lang/sh_puppet_output.min.js +22 -0
  36. data/public/js/sh_lang/sh_shell.min.js +1 -0
  37. data/public/js/showoff.js +423 -51
  38. data/views/404.erb +19 -0
  39. data/views/download.erb +36 -0
  40. data/views/header.erb +35 -25
  41. data/views/header_mini.erb +22 -0
  42. data/views/index.erb +46 -1
  43. data/views/onepage.erb +35 -14
  44. data/views/presenter.erb +63 -21
  45. data/views/stats.erb +73 -0
  46. metadata +170 -131
  47. data/public/css/960.css +0 -653
  48. data/public/css/pdf.css +0 -12
@@ -1,8 +1,17 @@
1
1
  // presenter js
2
- var w = null;
2
+ var slaveWindow = null;
3
+
4
+ var paceData = [];
5
+
6
+ $(document).ready(function(){
7
+ // attempt to open another window for the presentation. This may fail if
8
+ // popup blockers are enabled. In that case, the presenter needs to manually
9
+ // open the window by hitting the 'slave window' button.
10
+ openSlave();
11
+
12
+ // the presenter window doesn't need the reload on resize bit
13
+ $(window).unbind('resize');
3
14
 
4
- $(function(){
5
- w = window.open('/');
6
15
  // side menu accordian crap
7
16
  $("#preso").bind("showoff:loaded", function (event) {
8
17
  $(".menu > ul ul").hide()
@@ -10,35 +19,271 @@ $(function(){
10
19
  if ($(this).next().is('ul')) {
11
20
  $(this).next().toggle()
12
21
  } else {
13
- gotoSlide($(this).attr('rel'))
14
- w.gotoSlide($(this).attr('rel'))
15
- postSlide()
22
+ gotoSlide($(this).attr('rel'));
23
+ try { slaveWindow.gotoSlide($(this).attr('rel'), false) } catch (e) {}
24
+ postSlide();
25
+ update();
16
26
  }
17
- return false
18
- }).next().hide()
19
- })
27
+ return false;
28
+ }).next().hide();
29
+ });
30
+
31
+ $("#minStop").hide();
32
+ $("#startTimer").click(function() { toggleTimer() });
33
+ $("#stopTimer").click(function() { toggleTimer() });
34
+
35
+ /* zoom slide to match preview size, then set up resize handler. */
36
+ zoom();
37
+ $(window).resize(function() { zoom(); });
38
+
39
+ // set up tooltips
40
+ $('#report').tipsy({ offset: 5 });
41
+ $('#slaveWindow').tipsy({ offset: 5 });
42
+ $('#generatePDF').tipsy({ offset: 5 });
43
+ $('#onePage').tipsy({ offset: 5, gravity: 'ne' });
44
+
45
+ $('#stats').tipsy({ html: true, width: 450, trigger: 'manual', gravity: 'ne', opacity: 0.9, offset: 5 });
46
+ $('#downloads').tipsy({ html: true, width: 425, trigger: 'manual', gravity: 'ne', opacity: 0.9, offset: 5 });
47
+
48
+ $('#stats').click( function(e) { popupLoader( $(this), '/stats', 'stats', e); });
49
+ $('#downloads').click( function(e) { popupLoader( $(this), '/download', 'downloads', e); });
50
+
51
+ $('#enableFollower').tipsy({ gravity: 'ne' });
52
+ $('#enableRemote').tipsy();
53
+ $('#zoomer').tipsy({ gravity: 'ne' });
54
+
55
+ // Bind events for mobile viewing
56
+ $('#preso').unbind('tap').unbind('swipeleft').unbind('swiperight');
57
+
58
+ $('#preso').addSwipeEvents().
59
+ bind('tap', presNextStep). // next
60
+ bind('swipeleft', presNextStep). // next
61
+ bind('swiperight', presPrevStep); // prev
62
+
63
+ // set up the presenter modes
64
+ mode = { track: false, follow: true, update: true };
65
+
66
+ $('#remoteToggle').change( toggleFollower );
67
+ $('#followerToggle').change( toggleUpdater );
68
+
69
+ $('#topbar #update').click( function(e) {
70
+ e.preventDefault();
71
+ $.get("/getpage", function(data) {
72
+ gotoSlide(data);
73
+ });
74
+ });
75
+
76
+ setInterval(function() { updatePace() }, 1000);
77
+
78
+ // Tell the showoff server that we're a presenter
79
+ register();
20
80
  });
21
81
 
82
+ function popupLoader(elem, page, id, event)
83
+ {
84
+ var title = elem.attr('title');
85
+ event.preventDefault();
86
+
87
+ if(elem.attr('open') == 'true') {
88
+ elem.attr('open', false)
89
+ elem.tipsy("hide");
90
+ }
91
+ else {
92
+ $.get(page, function(data) {
93
+ var link = '<p class="newpage"><a href="' + page + '" target="_new">Open in new page...</a>';
94
+ var content = '<div id="' + id + '">' + $(data).find('#wrapper').html() + link + '</div>';
95
+
96
+ elem.attr('title', content);
97
+ elem.attr('open', true)
98
+ elem.tipsy("show");
99
+ setupStats();
100
+ });
101
+ }
102
+
103
+ return false;
104
+ }
105
+
106
+ function reportIssue() {
107
+ var slide = $("span#slideFile").text();
108
+ var issues = $("span#issueUrl").text();
109
+ var link = issues + encodeURIComponent('Issue with slide: ' + slide);
110
+ window.open(link);
111
+ }
112
+
113
+ function openSlave()
114
+ {
115
+ try {
116
+ if(slaveWindow == null || typeof(slaveWindow) == 'undefined' || slaveWindow.closed){
117
+ slaveWindow = window.open('/' + window.location.hash);
118
+ }
119
+ else if(slaveWindow.location.hash != window.location.hash) {
120
+ // maybe we need to reset content?
121
+ slaveWindow.location.href = '/' + window.location.hash;
122
+ }
123
+
124
+ // maintain the pointer back to the parent.
125
+ slaveWindow.presenterView = window;
126
+ slaveWindow.mode = { track: false, slave: true, follow: false };
127
+ }
128
+ catch(e) {
129
+ console.log('Failed to open or connect slave window. Popup blocker?');
130
+ }
131
+
132
+ // Set up a maintenance loop to keep the connection between windows. I wish there were a cleaner way to do this.
133
+ if (typeof maintainSlave == 'undefined') {
134
+ maintainSlave = setInterval(openSlave, 1000);
135
+ }
136
+ }
137
+
138
+ function askQuestion(question) {
139
+ $("#questions ul").prepend($('<li/>').text(question));
140
+ }
141
+
142
+ function paceFeedback(pace) {
143
+ var now = new Date();
144
+ switch(pace) {
145
+ case 'faster': paceData.push({time: now, pace: -1}); break; // too fast
146
+ case 'slower': paceData.push({time: now, pace: 1}); break; // too slow
147
+ }
148
+
149
+ updatePace();
150
+ }
151
+
152
+ function updatePace() {
153
+ // pace notices expire in a few minutes
154
+ cutoff = 3 * 60 * 1000;
155
+ expiration = new Date().getTime() - cutoff;
156
+
157
+ scale = 10; // this should max out around 5 clicks in either direction
158
+ sum = 50; // start in the middle
159
+
160
+ // Loops through and calculates a decaying average
161
+ for (var index = 0; index < paceData.length; index++) {
162
+ notice = paceData[index]
163
+
164
+ if(notice.time < expiration) {
165
+ paceData.splice( index, 1 );
166
+ }
167
+ else {
168
+ ratio = (notice.time - expiration) / cutoff;
169
+ sum += (notice.pace * scale * ratio);
170
+ }
171
+ }
172
+
173
+ position = Math.max(Math.min(sum, 90), 10); // between 10 and 90
174
+ console.log("Updating pace: " + position);
175
+ $("#paceMarker").css({ left: position+"%" });
176
+
177
+ if(position > 75) { $("#paceFast").show() } else { $("#paceFast").hide() }
178
+ if(position < 25) { $("#paceSlow").show() } else { $("#paceSlow").hide() }
179
+ }
180
+
181
+ function zoom()
182
+ {
183
+ if(window.innerWidth <= 480) {
184
+ $(".zoomed").css("zoom", 0.32);
185
+ }
186
+ else {
187
+ var hSlide = parseFloat($("#preso").height());
188
+ var wSlide = parseFloat($("#preso").width());
189
+ var hPreview = parseFloat($("#preview").height());
190
+ var wPreview = parseFloat($("#preview").width());
191
+ var factor = parseFloat($("#zoomer").val());
192
+
193
+ n = Math.min(hPreview/hSlide, wPreview/wSlide) - 0.04;
194
+
195
+ $(".zoomed").css("zoom", n*factor);
196
+ }
197
+ }
198
+
199
+ // extend this function to add presenter bits
200
+ var origGotoSlide = gotoSlide;
201
+ gotoSlide = function (slideNum)
202
+ {
203
+ origGotoSlide.call(this, slideNum)
204
+ try { slaveWindow.gotoSlide(slideNum, false) } catch (e) {}
205
+ postSlide()
206
+ }
207
+
208
+ // override with an alternate implementation.
209
+ // We need to do this before opening the websocket because the socket only
210
+ // inherits cookies present at initialization time.
211
+ reconnectControlChannel = function() {
212
+ $.ajax({
213
+ url: "presenter",
214
+ success: function() {
215
+ // In jQuery 1.4.2, this branch seems to be taken unconditionally. It doesn't
216
+ // matter though, as the disconnected() callback routes back here anyway.
217
+ console.log("Refreshing presenter cookie");
218
+ connectControlChannel();
219
+ },
220
+ error: function() {
221
+ console.log("Showoff server unavailable");
222
+ setTimeout(reconnectControlChannel(), 5000);
223
+ },
224
+ });
225
+ }
226
+
227
+ function update() {
228
+ if(mode.update) {
229
+ var slideName = $("#slideFile").text();
230
+ ws.send(JSON.stringify({ message: 'update', slide: slidenum, name: slideName}));
231
+ }
232
+ }
233
+
234
+ // Tell the showoff server that we're a presenter, giving the socket time to initialize
235
+ function register() {
236
+ setTimeout( function() {
237
+ try {
238
+ ws.send(JSON.stringify({ message: 'register' }));
239
+ }
240
+ catch(e) {
241
+ console.log("Registration failed. Sleeping");
242
+ // try again, until the socket finally lets us register
243
+ register();
244
+ }
245
+ }, 5000);
246
+ }
247
+
22
248
  function presPrevStep()
23
249
  {
24
- prevStep()
25
- w.prevStep()
26
- postSlide()
250
+ prevStep();
251
+ try { slaveWindow.prevStep(false) } catch (e) {};
252
+ postSlide();
253
+
254
+ update();
27
255
  }
28
256
 
29
257
  function presNextStep()
30
258
  {
31
- nextStep()
32
- w.nextStep()
33
- postSlide()
259
+ /* // I don't know what the point of this bit was, but it's not needed.
260
+ // read the variables set by our spawner
261
+ incrCurr = slaveWindow.incrCurr
262
+ incrSteps = slaveWindow.incrSteps
263
+ */
264
+ nextStep();
265
+ try { slaveWindow.nextStep(false) } catch (e) {};
266
+ postSlide();
267
+
268
+ update();
34
269
  }
35
270
 
36
271
  function postSlide()
37
272
  {
38
273
  if(currentSlide) {
39
- var notes = w.getCurrentNotes()
274
+ /*
275
+ try {
276
+ // whuuuu?
277
+ var notes = slaveWindow.getCurrentNotes()
278
+ }
279
+ catch(e) {
280
+ var notes = getCurrentNotes()
281
+ }
282
+ */
283
+ var notes = getCurrentNotes()
284
+ $('#notes').html(notes.html())
285
+
40
286
  var fileName = currentSlide.children().first().attr('ref')
41
- $('#notes').text(notes)
42
287
  $('#slideFile').text(fileName)
43
288
  }
44
289
  }
@@ -60,18 +305,20 @@ function keyDown(event)
60
305
  }
61
306
 
62
307
  if (key == 13) {
63
- if (gotoSlidenum > 0) {
64
- debug('go to ' + gotoSlidenum);
65
- slidenum = gotoSlidenum - 1;
66
- showSlide(true);
67
- w.slidenum = gotoSlidenum - 1;
68
- w.showSlide(true);
69
- gotoSlidenum = 0;
70
- } else {
71
- debug('executeCode');
72
- executeAnyCode();
73
- w.executeAnyCode();
74
- }
308
+ if (gotoSlidenum > 0) {
309
+ debug('go to ' + gotoSlidenum);
310
+ slidenum = gotoSlidenum - 1;
311
+ showSlide(true);
312
+ try {
313
+ slaveWindow.slidenum = gotoSlidenum - 1;
314
+ slaveWindow.showSlide(true);
315
+ } catch (e) {}
316
+ gotoSlidenum = 0;
317
+ } else {
318
+ debug('executeCode');
319
+ executeAnyCode();
320
+ try { slaveWindow.executeAnyCode(); } catch (e) {}
321
+ }
75
322
  }
76
323
 
77
324
  if (key == 16) // shift key
@@ -94,16 +341,20 @@ function keyDown(event)
94
341
  }
95
342
  else if (key == 37 || key == 33 || key == 38) // Left arrow, page up, or up arrow
96
343
  {
97
- presPrevStep()
344
+ presPrevStep();
98
345
  }
99
346
  else if (key == 39 || key == 34 || key == 40) // Right arrow, page down, or down arrow
100
347
  {
101
- presNextStep()
348
+ presNextStep();
102
349
  }
103
350
  else if (key == 84 || key == 67) // T or C for table of contents
104
351
  {
105
352
  $('#navmenu').toggle().trigger('click')
106
353
  }
354
+ else if (key == 83) // 's' for style
355
+ {
356
+ $('#stylemenu').toggle().trigger('click')
357
+ }
107
358
  else if (key == 90 || key == 191) // z or ? for help
108
359
  {
109
360
  $('#help').toggle()
@@ -119,11 +370,111 @@ function keyDown(event)
119
370
  else if (key == 27) // esc
120
371
  {
121
372
  removeResults();
122
- w.removeResults();
373
+ try { slaveWindow.removeResults(); } catch (e) {}
123
374
  }
124
375
  else if (key == 80) // 'p' for preshow
125
376
  {
126
- w.togglePreShow();
377
+ try { slaveWindow.togglePreShow(); } catch (e) {}
127
378
  }
128
379
  return true
129
380
  }
381
+
382
+ //* TIMER *//
383
+
384
+ var timerSetUp = false;
385
+ var timerRunning = false;
386
+ var intervalRunning = false;
387
+ var seconds = 0;
388
+ var totalMinutes = 35;
389
+
390
+ function toggleTimer()
391
+ {
392
+ if (!timerRunning) {
393
+ timerRunning = true
394
+ totalMinutes = parseInt($("#timerMinutes").attr('value'))
395
+ $("#minStart").hide()
396
+ $("#minStop").show()
397
+ $("#timerInfo").text(timerStatus(0));
398
+ seconds = 0
399
+ if (!intervalRunning) {
400
+ intervalRunning = true
401
+ setInterval(function() {
402
+ if (!timerRunning) { return; }
403
+ seconds++;
404
+ $("#timerInfo").text(timerStatus(seconds));
405
+ }, 1000); // fire every minute
406
+ }
407
+ } else {
408
+ seconds = 0
409
+ timerRunning = false
410
+ totalMinutes = 0
411
+ $("#timerInfo").text('')
412
+ $("#minStart").show()
413
+ $("#minStop").hide()
414
+ }
415
+ }
416
+
417
+ function timerStatus(seconds) {
418
+ var minutes = Math.round(seconds / 60);
419
+ var left = (totalMinutes - minutes);
420
+ var percent = Math.round((minutes / totalMinutes) * 100);
421
+ var progress = getSlidePercent() - percent;
422
+ setProgressColor(progress);
423
+ return minutes + '/' + left + ' - ' + percent + '%';
424
+ }
425
+
426
+ function setProgressColor(progress) {
427
+ ts = $('#timerSection')
428
+ ts.removeClass('tBlue')
429
+ ts.removeClass('tGreen')
430
+ ts.removeClass('tYellow')
431
+ ts.removeClass('tRed')
432
+ if(progress > 10) {
433
+ ts.addClass('tBlue')
434
+ } else if (progress > 0) {
435
+ ts.addClass('tGreen')
436
+ } else if (progress > -10) {
437
+ ts.addClass('tYellow')
438
+ } else {
439
+ ts.addClass('tRed')
440
+ }
441
+ }
442
+
443
+ var presSetCurrentStyle = setCurrentStyle;
444
+ var setCurrentStyle = function(style, prop) {
445
+ presSetCurrentStyle(style, false);
446
+ try { slaveWindow.setCurrentStyle(style, false); } catch (e) {}
447
+ }
448
+
449
+ function mobile() {
450
+ return ( navigator.userAgent.match(/Android/i)
451
+ || navigator.userAgent.match(/webOS/i)
452
+ || navigator.userAgent.match(/iPhone/i)
453
+ || navigator.userAgent.match(/iPad/i)
454
+ || navigator.userAgent.match(/iPod/i)
455
+ || navigator.userAgent.match(/BlackBerry/i)
456
+ || navigator.userAgent.match(/Windows Phone/i)
457
+ );
458
+ }
459
+
460
+ /********************
461
+ Follower Code
462
+ ********************/
463
+ function toggleFollower()
464
+ {
465
+ mode.follow = $("#remoteToggle").attr("checked");
466
+ getPosition();
467
+ }
468
+
469
+ function toggleUpdater()
470
+ {
471
+ mode.update = $("#followerToggle").attr("checked");
472
+ update();
473
+ }
474
+
475
+ /*
476
+ // redefine defaultMode
477
+ defaultMode = function() {
478
+ return mobile() ? modeState.follow : modeState.passive;
479
+ }
480
+ */
@@ -0,0 +1,112 @@
1
+ if (! this.sh_languages) {
2
+ this.sh_languages = {};
3
+ }
4
+ sh_languages['gherkin'] = [
5
+ [
6
+ [
7
+ /#/g,
8
+ 'sh_comment',
9
+ 1
10
+ ],
11
+ [
12
+ /\b[+-]?(?:(?:0x[A-Fa-f0-9]+)|(?:(?:[\d]*\.)?[\d]+(?:[eE][+-]?[\d]+)?))u?(?:(?:int(?:8|16|32|64))|L)?\b/g,
13
+ 'sh_number',
14
+ -1
15
+ ],
16
+ [
17
+ /^(?:[\s]*(?:But |And |Then |When |Given |\* |Scenarios|Examples|Scenario Template|Scenario Outline|Scenario|Background|Feature))/g,
18
+ 'sh_keyword',
19
+ -1
20
+ ],
21
+ [
22
+ /^(?:[\s]*'(?:[^\\']|\\.)*'[\s]*|[\s]*\"(?:[^\\\"]|\\.)*\"[\s]*)$/g,
23
+ 'sh_comment',
24
+ -1
25
+ ],
26
+ [
27
+ /(?:[\s]*'{3})/g,
28
+ 'sh_string',
29
+ 2
30
+ ],
31
+ [
32
+ /(?:[\s]*\"{3})/g,
33
+ 'sh_string',
34
+ 3
35
+ ],
36
+ [
37
+ /"/g,
38
+ 'sh_string',
39
+ 4
40
+ ],
41
+ [
42
+ /'/g,
43
+ 'sh_string',
44
+ 5
45
+ ],
46
+ [
47
+ /(?:@[^@\r\n\t ]+)/g,
48
+ 'sh_type',
49
+ -1
50
+ ],
51
+ [
52
+ /\|/g,
53
+ 'sh_specialchar',
54
+ -1
55
+ ]
56
+ ],
57
+ [
58
+ [
59
+ /$/g,
60
+ null,
61
+ -2
62
+ ]
63
+ ],
64
+ [
65
+ [
66
+ /(?:'{3})/g,
67
+ 'sh_string',
68
+ -2
69
+ ]
70
+ ],
71
+ [
72
+ [
73
+ /(?:\"{3})/g,
74
+ 'sh_string',
75
+ -2
76
+ ]
77
+ ],
78
+ [
79
+ [
80
+ /$/g,
81
+ null,
82
+ -2
83
+ ],
84
+ [
85
+ /\\(?:\\|")/g,
86
+ null,
87
+ -1
88
+ ],
89
+ [
90
+ /"/g,
91
+ 'sh_string',
92
+ -2
93
+ ]
94
+ ],
95
+ [
96
+ [
97
+ /$/g,
98
+ null,
99
+ -2
100
+ ],
101
+ [
102
+ /\\(?:\\|')/g,
103
+ null,
104
+ -1
105
+ ],
106
+ [
107
+ /'/g,
108
+ 'sh_string',
109
+ -2
110
+ ]
111
+ ]
112
+ ];
@@ -0,0 +1 @@
1
+ if(!this.sh_languages){this.sh_languages={}}sh_languages.gherkin=[[[/#/g,"sh_comment",1],[/\b[+-]?(?:(?:0x[A-Fa-f0-9]+)|(?:(?:[\d]*\.)?[\d]+(?:[eE][+-]?[\d]+)?))u?(?:(?:int(?:8|16|32|64))|L)?\b/g,"sh_number",-1],[/^(?:[\s]*(?:But |And |Then |When |Given |\* |Scenarios|Examples|Scenario Template|Scenario Outline|Scenario|Background|Feature))/g,"sh_keyword",-1],[/^(?:[\s]*'(?:[^\\']|\\.)*'[\s]*|[\s]*\"(?:[^\\\"]|\\.)*\"[\s]*)$/g,"sh_comment",-1],[/(?:[\s]*'{3})/g,"sh_string",2],[/(?:[\s]*\"{3})/g,"sh_string",3],[/"/g,"sh_string",4],[/'/g,"sh_string",5],[/(?:@[^@\r\n\t ]+)/g,"sh_type",-1],[/\|/g,"sh_specialchar",-1]],[[/$/g,null,-2]],[[/(?:'{3})/g,"sh_string",-2]],[[/(?:\"{3})/g,"sh_string",-2]],[[/$/g,null,-2],[/\\(?:\\|")/g,null,-1],[/"/g,"sh_string",-2]],[[/$/g,null,-2],[/\\(?:\\|')/g,null,-1],[/'/g,"sh_string",-2]]];
@@ -0,0 +1,87 @@
1
+ if (! this.sh_languages) {
2
+ this.sh_languages = {};
3
+ }
4
+ sh_languages['ini'] = [
5
+ [
6
+ [
7
+ /\[[A-Za-z0-9]+\]/g,
8
+ 'sh_param',
9
+ -1
10
+ ],
11
+ [
12
+ /\b[A-Za-z0-9]+(?=\s*=)/g,
13
+ 'sh_attribute',
14
+ -1
15
+ ],
16
+ [
17
+ /(?:^\=begin)/g,
18
+ 'sh_comment',
19
+ 4
20
+ ],
21
+ [
22
+ /#/g,
23
+ 'sh_comment',
24
+ 1
25
+ ]
26
+ ],
27
+ [
28
+ [
29
+ /$/g,
30
+ null,
31
+ -2
32
+ ],
33
+ [
34
+ /\\(?:\\|")/g,
35
+ null,
36
+ -1
37
+ ],
38
+ [
39
+ /"/g,
40
+ 'sh_string',
41
+ -2
42
+ ]
43
+ ],
44
+ [
45
+ [
46
+ /$/g,
47
+ null,
48
+ -2
49
+ ],
50
+ [
51
+ /\\(?:\\|')/g,
52
+ null,
53
+ -1
54
+ ],
55
+ [
56
+ /'/g,
57
+ 'sh_string',
58
+ -2
59
+ ]
60
+ ],
61
+ [
62
+ [
63
+ /$/g,
64
+ null,
65
+ -2
66
+ ],
67
+ [
68
+ />/g,
69
+ 'sh_string',
70
+ -2
71
+ ]
72
+ ],
73
+ [
74
+ [
75
+ /^(?:\=end)/g,
76
+ 'sh_comment',
77
+ 5
78
+ ]
79
+ ],
80
+ [
81
+ [
82
+ /$/g,
83
+ null,
84
+ -2
85
+ ]
86
+ ]
87
+ ];