applied helper patch kindly submitted by skaar <skaar@waste.org>
[rubygit.git] / camping / gitweb.rb
blob54ed91c0566ded0bbb5619f114d346d676ce8723
1 require 'rubygems'
2 require 'camping'
3 require 'lib/git'
6 # gitweb is a web frontend on git
7 # there is no user auth, so don't run this anywhere that anyone can use it
8 # it's read only, but anyone can remove or add references to your repos
10 # everything but the archive and diff functions are now in pure ruby
12 # install dependencies
13 #   sudo gem install camping-omnibus --source http://code.whytheluckystiff.net
15 # todo
16 #   - diff/patch between any two objects
17 #     - expand patch to entire file
18 #   - set title properly
19 #   - grep / search function
20 #   - prettify : http://projects.wh.techno-weenie.net/changesets/3030
21 #   - add user model (add/remove repos)
22 #   - implement http-push for authenticated users 
24 # author : scott chacon
27 Camping.goes :GitWeb
29 module GitWeb::Models
30   class Repository < Base; end
31   
32   class CreateGitWeb < V 0.1
33     def self.up
34       create_table :gitweb_repositories, :force => true do |t|
35         t.column :name,  :string 
36         t.column :path,  :string 
37         t.column :bare,  :boolean 
38       end
39     end
40   end
41 end
43 module GitWeb::Helpers
44   def inline_data(identifier)
45     section = "__#{identifier.to_s.upcase}__"
46     @@inline_data ||= File.read(__FILE__).gsub(/.*__END__/m, '')
47     data = @@inline_data.match(/(#{section}.)(.*?)((__)|(\Z))/m)
48     data ? data[2] : nil # return nil if no second found
49   end
50 end
52 module GitWeb::Controllers
54   class Stylesheet < R '/css/highlight.css'
55     def get
56       @headers['Content-Type'] = 'text/css'
57       inline_data(:css)
58     end
59   end
60   
61   class JsHighlight < R '/js/highlight.js'
62     def get
63       @headers['Content-Type'] = 'text/css'
64       inline_data(:js)
65     end
66   end
67   
68   
69   class Index < R '/'
70     def get
71       @repos = Repository.find :all
72       render :index
73     end
74   end
75     
76   class Add < R '/add'
77     def get
78       @repo = Repository.new
79       render :add
80     end
81     def post
82       if Git.bare(input.repository_path)
83         repo = Repository.create :name => input.repo_name, :path => input.repo_path, :bare => input.repo_bare
84         redirect View, repo
85       else
86         redirect Index
87       end
88     end
89   end
90   
91   class RemoveRepo < R '/remove/(\d+)'
92     def get repo_id
93       @repo = Repository.find repo_id
94       @repo.destroy
95       @repos = Repository.find :all
96       render :index
97     end
98   end
99   
100   
101   class View < R '/view/(\d+)'
102     def get repo_id
103       @repo = Repository.find repo_id
104       @git = Git.bare(@repo.path)     
105       render :view
106     end
107   end
108   
109   class Fetch < R '/git/(\d+)/(.*)'
110     def get repo_id, path
111       @repo = Repository.find repo_id
112       @git = Git.bare(@repo.path)
113       File.read(File.join(@git.repo.path, path))
114     end
115   end
116   
117   class Commit < R '/commit/(\d+)/(\w+)'
118     def get repo_id, sha
119       @repo = Repository.find repo_id
120       @git = Git.bare(@repo.path)   
121       @commit = @git.gcommit(sha)
122       render :commit
123     end
124   end
125   
126   class Tree < R '/tree/(\d+)/(\w+)'
127     def get repo_id, sha
128       @repo = Repository.find repo_id
129       @git = Git.bare(@repo.path)   
130       @tree = @git.gtree(sha)
131       render :tree
132     end
133   end
134   
135   class Blob < R '/blob/(\d+)/(.*?)/(\w+)'
136     def get repo_id, file, sha
137       @repo = Repository.find repo_id
139       #logger = Logger.new('/tmp/git.log')
140       #logger.level = Logger::INFO
141       #@git = Git.bare(@repo.path, :log => logger)      
143       @git = Git.bare(@repo.path)
144       @blob = @git.gblob(sha)
145       @file = file
146       render :blob
147     end
148   end  
149   
150   class BlobRaw < R '/blob/(\d+)/(\w+)'
151      def get repo_id, sha
152        @repo = Repository.find repo_id
153        @git = Git.bare(@repo.path)      
154        @blob = @git.gblob(sha)
155        @blob.contents
156      end
157   end
158   
159   class Archive < R '/archive/(\d+)/(\w+)'
160     def get repo_id, sha
161       @repo = Repository.find repo_id
162       @git = Git.bare(@repo.path)
163       
164       file = @git.gtree(sha).archive
165       @headers['Content-Type'] = 'application/zip'
166       @headers["Content-Disposition"] = "attachment; filename=archive.zip"
167       File.new(file).read
168     end
169   end
171   class Download < R '/download/(\d+)/(.*?)/(\w+)'
172     def get repo_id, file, sha
173       @repo = Repository.find repo_id
174       @git = Git.bare(@repo.path)
175       @headers["Content-Disposition"] = "attachment; filename=#{file}"
176       @git.gblob(sha).contents
177     end
178   end
179   
180   class Diff < R '/diff/(\d+)/(\w+)/(\w+)'
181     def get repo_id, tree1, tree2
182       @repo = Repository.find repo_id
183       @git = Git.bare(@repo.path)
184       @tree1 = tree1
185       @tree2 = tree2
186       @diff = @git.diff(tree2, tree1)
187       render :diff
188     end
189   end
190   
191   class Patch < R '/patch/(\d+)/(\w+)/(\w+)'
192     def get repo_id, tree1, tree2
193       @repo = Repository.find repo_id
194       @git = Git.bare(@repo.path)
195       @diff = @git.diff(tree1, tree2).patch
196     end
197   end
198   
201 module GitWeb::Views
202   def layout
203     html do
204       head do
205         title 'test'
206         link :href=>R(Stylesheet), :rel=>'stylesheet', :type=>'text/css'
207         script '', :type => "text/javascript", :language => "JavaScript", :src => R(JsHighlight)
208       end
209       style <<-END, :type => 'text/css'
210         body { font-family: verdana, arial, helvetica, sans-serif; color: #333; 
211                 font-size:   13px;
212                 line-height: 18px;}
214         h1 { background: #cce; padding: 10px; margin: 3px; }
215         h3 { background: #aea; padding: 5px; margin: 3px; }
216         .options { float: right; margin: 10px; }
217         p { padding: 5px; }
218         .odd { background: #eee; }
219         .tag { margin: 5px; padding: 1px 3px; border: 1px solid #8a8; background: #afa;}
220         .indent { padding: 0px 15px;}
221         table tr td { font-size: 13px; }
222         table.shortlog { width: 100%; }
223         .timer { color: #666; padding: 10px; margin-top: 10px; }
224       END
225       body :onload => "sh_highlightDocument();" do
226         before = Time.now().usec
227         self << yield
228         self << '<br/>' + ((Time.now().usec - before).to_f / 60).to_s + ' sec'
229       end
230     end
231   end
233   # git repo views
234   
235   def view
236     h1 @repo.name
237     h2 @repo.path
239     gtags = @git.tags
240     @tags = {}
241     gtags.each { |tag| @tags[tag.sha] ||= []; @tags[tag.sha] << tag.name }
242     
243     url = 'http:' + URL(Fetch, @repo.id, '').to_s
245     h3 'info'
246     table.info do
247       tr { td 'owner: '; td @git.config('user.name') }
248       tr { td 'email: '; td @git.config('user.email') }
249       tr { td 'url: '; td { a url, :href => url } }
250     end
251     
252     h3 'shortlog'
253     table.shortlog do
254       @git.log.each do |log|
255         tr do
256           td log.date.strftime("%Y-%m-%d")
257           td { code log.sha[0, 8] }
258           td { em log.author.name }
259           td do
260             span.message log.message[0, 60]
261             @tags[log.sha].each do |t|
262               span.space ' '
263               span.tag { code t }
264             end if @tags[log.sha]
265           end
266           td { a 'commit', :href => R(Commit, @repo, log.sha) }
267           td { a 'commit-diff', :href => R(Diff, @repo, log.sha, log.parent.sha) }
268           td { a 'tree', :href => R(Tree, @repo, log.gtree.sha) }
269           td { a 'archive', :href => R(Archive, @repo, log.gtree.sha) }
270         end
271       end
272     end
273     
274     h3 'branches'
275     @git.branches.each do |branch|
276       li { a branch.full, :href => R(Commit, @repo, branch.gcommit.sha) }
277     end
278     
279     h3 'tags'
280     gtags.each do |tag|
281       li { a tag.name, :href => R(Commit, @repo, tag.sha) }
282     end
283     
284   end
285   
286   def commit
287     a.options 'repo', :href => R(View, @repo)
288     h1 @commit.name
289     h3 'info'
290     table.info do
291       tr { td 'author: '; td @commit.author.name + ' <' + @commit.author.email + '>'}
292       tr { td ''; td { code @commit.author.date } }
293       tr { td 'committer: '; td @commit.committer.name + ' <' + @commit.committer.email + '>'}
294       tr { td ''; td { code @commit.committer.date } }
295       tr { td 'commit sha: '; td { code @commit.sha } }
296       tr do
297         td 'tree sha: '
298         td do 
299           code { a @commit.gtree.sha, :href => R(Tree, @repo, @commit.gtree.sha) }
300           span.space ' '
301           a 'archive', :href => R(Archive, @repo, @commit.gtree.sha)
302         end
303       end
304       tr do
305         td 'parents: '
306         td do
307           @commit.parents.each do |p|
308             code { a p.sha, :href => R(Commit, @repo, p.sha) }
309             span.space ' '
310             a 'diff', :href => R(Diff, @repo, p.sha, @commit.sha)
311             span.space ' '
312             a 'archive', :href => R(Archive, @repo, p.gtree.sha)            
313             br
314           end
315         end
316       end
317     end
318     h3 'commit message'
319     p @commit.message
320   end
321   
322   def tree
323     a.options 'repo', :href => R(View, @repo)
324     h3 'tree : ' + @tree.sha
325     p { a 'archive tree', :href => R(Archive, @repo, @tree.sha) }; 
326     table do
327       @tree.children.each do |file, node|
328         tr :class => cycle('odd','even') do
329           td { code node.sha[0, 8] }
330           td node.mode
331           td file
332           if node.type == 'tree'
333             td { a node.type, :href => R(Tree, @repo, node.sha) }
334             td { a 'archive', :href => R(Archive, @repo, node.sha) }
335           else
336             td { a node.type, :href => R(Blob, @repo, file, node.sha) }
337             td { a 'raw', :href => R(BlobRaw, @repo, node.sha) }
338           end
339         end
340       end
341     end 
342   end
344   def blob
345     ext = File.extname(@file).gsub('.', '')
346     
347     case ext
348       when 'rb' : classnm = 'sh_ruby'
349       when 'js' : classnm = 'sh_javascript'
350       when 'html' : classnm = 'sh_html'
351       when 'css' : classnm = 'sh_css'
352     end
353     
354     a.options 'repo', :href => R(View, @repo)
355     h3 'blob : ' + @blob.sha
356     h4 @file
357     
358     a 'download file', :href => R(Download, @repo, @file, @blob.sha)
359     
360     div.indent { pre @blob.contents, :class => classnm }
361   end
362   
363   def diff
364     a.options 'repo', :href => R(View, @repo)    
365     h1 "diff"
367     p { a 'download patch file', :href => R(Patch, @repo, @tree1, @tree2) }
369     p do
370       a @tree1, :href => R(Tree, @repo, @tree1)
371       span.space ' : '
372       a @tree2, :href => R(Tree, @repo, @tree2)
373     end
374     
375     @diff.each do |file|
376       h3 file.path
377       div.indent { pre file.patch, :class => 'sh_diff' }
378     end
379   end
380   
381   # repo management views
382   
383   def add
384     _form(@repo)
385   end
386   
387   def _form(repo)
388     form(:method => 'post') do
389       label 'Path', :for => 'repo_path'; br
390       input :name => 'repo_path', :type => 'text', :value => repo.path; br
392       label 'Name', :for => 'repo_name'; br
393       input :name => 'repo_name', :type => 'text', :value => repo.name; br
395       label 'Bare', :for => 'repo_bare'; br
396       input :type => 'checkbox', :name => 'repo_bare', :value => repo.bare; br
398       input :type => 'hidden', :name => 'repo_id', :value => repo.id
399       input :type => 'submit'
400     end
401   end
402   
403   def index
404     @repos.each do | repo |
405       h1 repo.name
406       a 'remove', :href => R(RemoveRepo, repo.id)
407       span.space ' '
408       a repo.path, :href => R(View, repo.id)
409     end
410     br
411     br
412     a 'add new repo', :href => R(Add)
413   end
414   
415   # convenience functions
416   
417   def cycle(v1, v2)
418     (@value == v1) ? @value = v2 : @value = v1
419     @value
420   end
421   
424 def GitWeb.create
425   GitWeb::Models.create_schema
428 # everything below this line is the css and javascript for syntax-highlighting
429 __END__
431 __CSS__
432 pre.sh_sourceCode {
433   background-color: white;
434   color: black;
435   font-style: normal;
436   font-weight: normal;
439 pre.sh_sourceCode .sh_keyword { color: blue; font-weight: bold; }           /* language keywords */
440 pre.sh_sourceCode .sh_type { color: darkgreen; }                            /* basic types */
441 pre.sh_sourceCode .sh_string { color: red; font-family: monospace; }        /* strings and chars */
442 pre.sh_sourceCode .sh_regexp { color: orange; font-family: monospace; }     /* regular expressions */
443 pre.sh_sourceCode .sh_specialchar { color: pink; font-family: monospace; }  /* e.g., \n, \t, \\ */
444 pre.sh_sourceCode .sh_comment { color: brown; font-style: italic; }         /* comments */
445 pre.sh_sourceCode .sh_number { color: purple; }                             /* literal numbers */
446 pre.sh_sourceCode .sh_preproc { color: darkblue; font-weight: bold; }       /* e.g., #include, import */
447 pre.sh_sourceCode .sh_symbol { color: darkred; }                            /* e.g., <, >, + */
448 pre.sh_sourceCode .sh_function { color: black; font-weight: bold; }         /* function calls and declarations */
449 pre.sh_sourceCode .sh_cbracket { color: red; }                              /* block brackets (e.g., {, }) */
450 pre.sh_sourceCode .sh_todo { font-weight: bold; background-color: cyan; }   /* TODO and FIXME */
452 /* for Perl, PHP, Prolog, Python, shell, Tcl */
453 pre.sh_sourceCode .sh_variable { color: darkgreen; }
455 /* line numbers (not yet implemented) */
456 pre.sh_sourceCode .sh_linenum { color: black; font-family: monospace; }
458 /* Internet related */
459 pre.sh_sourceCode .sh_url { color: blue; text-decoration: underline; font-family: monospace; }
461 /* for ChangeLog and Log files */
462 pre.sh_sourceCode .sh_date { color: blue; font-weight: bold; }
463 pre.sh_sourceCode .sh_time, pre.sh_sourceCode .sh_file { color: darkblue; font-weight: bold; }
464 pre.sh_sourceCode .sh_ip, pre.sh_sourceCode .sh_name { color: darkgreen; }
466 /* for LaTeX */
467 pre.sh_sourceCode .sh_italics { color: darkgreen; font-style: italic; }
468 pre.sh_sourceCode .sh_bold { color: darkgreen; font-weight: bold; }
469 pre.sh_sourceCode .sh_underline { color: darkgreen; text-decoration: underline; }
470 pre.sh_sourceCode .sh_fixed { color: green; font-family: monospace; }
471 pre.sh_sourceCode .sh_argument { color: darkgreen; }
472 pre.sh_sourceCode .sh_optionalargument { color: purple; }
473 pre.sh_sourceCode .sh_math { color: orange; }
474 pre.sh_sourceCode .sh_bibtex { color: blue; }
476 /* for diffs */
477 pre.sh_sourceCode .sh_oldfile { color: orange; }
478 pre.sh_sourceCode .sh_newfile { color: darkgreen; }
479 pre.sh_sourceCode .sh_difflines { color: blue; }
481 /* for css */
482 pre.sh_sourceCode .sh_selector { color: purple; }
483 pre.sh_sourceCode .sh_property { color: blue; }
484 pre.sh_sourceCode .sh_value { color: darkgreen; font-style: italic; }
486 __JS__
488 /* Copyright (C) 2007 gnombat@users.sourceforge.net */
489 /* License: http://shjs.sourceforge.net/doc/license.html */
491 function sh_highlightString(inputString,language,builder){var patternStack={_stack:[],getLength:function(){return this._stack.length;},getTop:function(){var stack=this._stack;var length=stack.length;if(length===0){return undefined;}
492 return stack[length-1];},push:function(state){this._stack.push(state);},pop:function(){if(this._stack.length===0){throw"pop on empty stack";}
493 this._stack.pop();}};var pos=0;var currentStyle=undefined;var output=function(s,style){var length=s.length;if(length===0){return;}
494 if(!style){var pattern=patternStack.getTop();if(pattern!==undefined&&!('state'in pattern)){style=pattern.style;}}
495 if(currentStyle!==style){if(currentStyle){builder.endElement();}
496 if(style){builder.startElement(style);}}
497 builder.text(s);pos+=length;currentStyle=style;};var endOfLinePattern=/\r\n|\r|\n/g;endOfLinePattern.lastIndex=0;var inputStringLength=inputString.length;while(pos<inputStringLength){var start=pos;var end;var startOfNextLine;var endOfLineMatch=endOfLinePattern.exec(inputString);if(endOfLineMatch===null){end=inputStringLength;startOfNextLine=inputStringLength;}
498 else{end=endOfLineMatch.index;startOfNextLine=endOfLinePattern.lastIndex;}
499 var line=inputString.substring(start,end);var matchCache=null;var matchCacheState=-1;for(;;){var posWithinLine=pos-start;var pattern=patternStack.getTop();var stateIndex=pattern===undefined?0:pattern.next;var state=language[stateIndex];var numPatterns=state.length;if(stateIndex!==matchCacheState){matchCache=[];}
500 var bestMatch=null;var bestMatchIndex=-1;for(var i=0;i<numPatterns;i++){var match;if(stateIndex===matchCacheState&&(matchCache[i]===null||posWithinLine<=matchCache[i].index)){match=matchCache[i];}
501 else{var regex=state[i].regex;regex.lastIndex=posWithinLine;match=regex.exec(line);matchCache[i]=match;}
502 if(match!==null&&(bestMatch===null||match.index<bestMatch.index)){bestMatch=match;bestMatchIndex=i;}}
503 matchCacheState=stateIndex;if(bestMatch===null){output(line.substring(posWithinLine),null);break;}
504 else{if(bestMatch.index>posWithinLine){output(line.substring(posWithinLine,bestMatch.index),null);}
505 pattern=state[bestMatchIndex];var newStyle=pattern.style;var matchedString;if(newStyle instanceof Array){for(var subexpression=0;subexpression<newStyle.length;subexpression++){matchedString=bestMatch[subexpression+1];output(matchedString,newStyle[subexpression]);}}
506 else{matchedString=bestMatch[0];output(matchedString,newStyle);}
507 if('next'in pattern){patternStack.push(pattern);}
508 else{if('exit'in pattern){patternStack.pop();}
509 if('exitall'in pattern){while(patternStack.getLength()>0){patternStack.pop();}}}}}
510 if(currentStyle){builder.endElement();}
511 currentStyle=undefined;if(endOfLineMatch){builder.text(endOfLineMatch[0]);}
512 pos=startOfNextLine;}}
513 function sh_getClasses(element){var result=[];var htmlClass=element.className;if(htmlClass&&htmlClass.length>0){var htmlClasses=htmlClass.split(" ");for(var i=0;i<htmlClasses.length;i++){if(htmlClasses[i].length>0){result.push(htmlClasses[i]);}}}
514 return result;}
515 function sh_addClass(element,name){var htmlClasses=sh_getClasses(element);for(var i=0;i<htmlClasses.length;i++){if(name.toLowerCase()===htmlClasses[i].toLowerCase()){return;}}
516 htmlClasses.push(name);element.className=htmlClasses.join(" ");}
517 function sh_getText(element){if(element.nodeType===3||element.nodeType===4){return element.data;}
518 else if(element.childNodes.length===1){return sh_getText(element.firstChild);}
519 else{var result='';for(var i=0;i<element.childNodes.length;i++){result+=sh_getText(element.childNodes.item(i));}
520 return result;}}
521 function sh_isEmailAddress(url){if(/^mailto:/.test(url)){return false;}
522 return url.indexOf('@')!==-1;}
523 var sh_builder={init:function(htmlDocument,element){while(element.hasChildNodes()){element.removeChild(element.firstChild);}
524 this._document=htmlDocument;this._element=element;this._currentText=null;this._documentFragment=htmlDocument.createDocumentFragment();this._currentParent=this._documentFragment;this._span=htmlDocument.createElement("span");this._a=htmlDocument.createElement("a");},startElement:function(style){if(this._currentText!==null){this._currentParent.appendChild(this._document.createTextNode(this._currentText));this._currentText=null;}
525 var span=this._span.cloneNode(true);span.className=style;this._currentParent.appendChild(span);this._currentParent=span;},endElement:function(){if(this._currentText!==null){if(this._currentParent.className==='sh_url'){var a=this._a.cloneNode(true);a.className='sh_url';var url=this._currentText;if(url.length>0&&url.charAt(0)==='<'&&url.charAt(url.length-1)==='>'){url=url.substr(1,url.length-2);}
526 if(sh_isEmailAddress(url)){url='mailto:'+url;}
527 a.setAttribute('href',url);a.appendChild(this._document.createTextNode(this._currentText));this._currentParent.appendChild(a);}
528 else{this._currentParent.appendChild(this._document.createTextNode(this._currentText));}
529 this._currentText=null;}
530 this._currentParent=this._currentParent.parentNode;},text:function(s){if(this._currentText===null){this._currentText=s;}
531 else{this._currentText+=s;}},close:function(){if(this._currentText!==null){this._currentParent.appendChild(this._document.createTextNode(this._currentText));this._currentText=null;}
532 this._element.appendChild(this._documentFragment);}};function sh_highlightElement(htmlDocument,element,language){sh_addClass(element,"sh_sourceCode");var inputString;if(element.childNodes.length===0){return;}
533 else{inputString=sh_getText(element);}
534 sh_builder.init(htmlDocument,element);sh_highlightString(inputString,language,sh_builder);sh_builder.close();}
535 function sh_highlightHTMLDocument(htmlDocument){if(!window.sh_languages){return;}
536 var nodeList=htmlDocument.getElementsByTagName("pre");for(var i=0;i<nodeList.length;i++){var element=nodeList.item(i);var htmlClasses=sh_getClasses(element);for(var j=0;j<htmlClasses.length;j++){var htmlClass=htmlClasses[j].toLowerCase();if(htmlClass==="sh_sourcecode"){continue;}
537 var prefix=htmlClass.substr(0,3);if(prefix==="sh_"){var language=htmlClass.substring(3);if(language in sh_languages){sh_highlightElement(htmlDocument,element,sh_languages[language]);}
538 else{throw"Found <pre> element with class='"+htmlClass+"', but no such language exists";}}}}}
539 function sh_highlightDocument(){sh_highlightHTMLDocument(document);}
541 if(!this.sh_languages){this.sh_languages={};}
542 sh_languages['css']=[[{'next':1,'regex':/\/\/\//g,'style':'sh_comment'},{'next':7,'regex':/\/\//g,'style':'sh_comment'},{'next':8,'regex':/\/\*\*/g,'style':'sh_comment'},{'next':14,'regex':/\/\*/g,'style':'sh_comment'},{'regex':/(?:\.|#)[A-Za-z0-9_]+/g,'style':'sh_selector'},{'next':15,'regex':/\{/g,'state':1,'style':'sh_cbracket'},{'regex':/~|!|%|\^|\*|\(|\)|-|\+|=|\[|\]|\\|:|;|,|\.|\/|\?|&|<|>|\|/g,'style':'sh_symbol'}],[{'exit':true,'regex':/$/g},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'next':2,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':4,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':5,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'},{'regex':/@[A-Za-z]+/g,'style':'sh_type'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':3,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':4,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':6,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/\*\//g,'style':'sh_comment'},{'next':8,'regex':/\/\*\*/g,'style':'sh_comment'},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'next':9,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':11,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':12,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'},{'regex':/@[A-Za-z]+/g,'style':'sh_type'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':10,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':11,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':13,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/\*\//g,'style':'sh_comment'},{'next':14,'regex':/\/\*/g,'style':'sh_comment'},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/\}/g,'style':'sh_cbracket'},{'next':16,'regex':/\/\/\//g,'style':'sh_comment'},{'next':22,'regex':/\/\//g,'style':'sh_comment'},{'next':23,'regex':/\/\*\*/g,'style':'sh_comment'},{'next':29,'regex':/\/\*/g,'style':'sh_comment'},{'regex':/[A-Za-z0-9_-]+[ \t]*:/g,'style':'sh_property'},{'regex':/[.%A-Za-z0-9_-]+/g,'style':'sh_value'},{'regex':/#(?:[A-Za-z0-9_]+)/g,'style':'sh_string'}],[{'exit':true,'regex':/$/g},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'next':17,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':19,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':20,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'},{'regex':/@[A-Za-z]+/g,'style':'sh_type'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':18,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':19,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':21,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/\*\//g,'style':'sh_comment'},{'next':23,'regex':/\/\*\*/g,'style':'sh_comment'},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'next':24,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':26,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':27,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'},{'regex':/@[A-Za-z]+/g,'style':'sh_type'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':25,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':26,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':28,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/\*\//g,'style':'sh_comment'},{'next':29,'regex':/\/\*/g,'style':'sh_comment'},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}]];
544 if(!this.sh_languages){this.sh_languages={};}
545 sh_languages['diff']=[[{'next':1,'regex':/(?=^[-]{3})/g,'state':1,'style':'sh_oldfile'},{'next':6,'regex':/(?=^[*]{3})/g,'state':1,'style':'sh_oldfile'},{'next':14,'regex':/(?=^[\d])/g,'state':1,'style':'sh_difflines'}],[{'next':2,'regex':/^[-]{3}/g,'style':'sh_oldfile'},{'next':3,'regex':/^[-]/g,'style':'sh_oldfile'},{'next':4,'regex':/^[+]/g,'style':'sh_newfile'},{'next':5,'regex':/^@@/g,'style':'sh_difflines'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'next':7,'regex':/^[*]{3}[ \t]+[\d]/g,'style':'sh_oldfile'},{'next':9,'regex':/^[*]{3}/g,'style':'sh_oldfile'},{'next':10,'regex':/^[-]{3}[ \t]+[\d]/g,'style':'sh_newfile'},{'next':13,'regex':/^[-]{3}/g,'style':'sh_newfile'}],[{'next':8,'regex':/^[\s]/g,'style':'sh_normal'},{'exit':true,'regex':/(?=^[-]{3})/g,'style':'sh_newfile'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'next':11,'regex':/^[\s]/g,'style':'sh_normal'},{'exit':true,'regex':/(?=^[*]{3})/g,'style':'sh_newfile'},{'exit':true,'next':12,'regex':/^diff/g,'style':'sh_normal'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'next':15,'regex':/^[\d]/g,'style':'sh_difflines'},{'next':16,'regex':/^[<]/g,'style':'sh_oldfile'},{'next':17,'regex':/^[>]/g,'style':'sh_newfile'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g}]];
547 if(!this.sh_languages){this.sh_languages={};}
548 sh_languages['html']=[[{'next':1,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':3,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':4,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':2,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':3,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':5,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}]];
550 if(!this.sh_languages){this.sh_languages={};}
551 sh_languages['javascript']=[[{'regex':/\b(?:import|package)\b/g,'style':'sh_preproc'},{'next':1,'regex':/\/\/\//g,'style':'sh_comment'},{'next':7,'regex':/\/\//g,'style':'sh_comment'},{'next':8,'regex':/\/\*\*/g,'style':'sh_comment'},{'next':14,'regex':/\/\*/g,'style':'sh_comment'},{'regex':/\b[+-]?(?:(?:0x[A-Fa-f0-9]+)|(?:(?:[\d]*\.)?[\d]+(?:[eE][+-]?[\d]+)?))u?(?:(?:int(?:8|16|32|64))|L)?\b/g,'style':'sh_number'},{'next':15,'regex':/"/g,'style':'sh_string'},{'next':16,'regex':/'/g,'style':'sh_string'},{'regex':/(\b(?:class|interface))([ \t]+)([$A-Za-z0-9]+)/g,'style':['sh_keyword','sh_normal','sh_type']},{'regex':/\b(?:abstract|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|final|finally|for|function|goto|if|implements|in|instanceof|interface|native|new|null|private|protected|prototype|public|return|static|super|switch|synchronized|throw|throws|this|transient|true|try|typeof|var|volatile|while|with)\b/g,'style':'sh_keyword'},{'regex':/\b(?:int|byte|boolean|char|long|float|double|short|void)\b/g,'style':'sh_type'},{'regex':/~|!|%|\^|\*|\(|\)|-|\+|=|\[|\]|\\|:|;|,|\.|\/|\?|&|<|>|\|/g,'style':'sh_symbol'},{'regex':/\{|\}/g,'style':'sh_cbracket'},{'regex':/(?:[A-Za-z]|_)[A-Za-z0-9_]*[ \t]*(?=\()/g,'style':'sh_function'}],[{'exit':true,'regex':/$/g},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'next':2,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':4,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':5,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'},{'regex':/@[A-Za-z]+/g,'style':'sh_type'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':3,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':4,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':6,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/\*\//g,'style':'sh_comment'},{'next':8,'regex':/\/\*\*/g,'style':'sh_comment'},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'next':9,'regex':/<!DOCTYPE/g,'state':1,'style':'sh_preproc'},{'next':11,'regex':/<!--/g,'style':'sh_comment'},{'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*(?:\/)?>/g,'style':'sh_keyword'},{'next':12,'regex':/<(?:\/)?[A-Za-z][A-Za-z0-9]*/g,'state':1,'style':'sh_keyword'},{'regex':/&(?:[A-Za-z0-9]+);/g,'style':'sh_preproc'},{'regex':/@[A-Za-z]+/g,'style':'sh_type'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/>/g,'style':'sh_preproc'},{'next':10,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/-->/g,'style':'sh_comment'},{'next':11,'regex':/<!--/g,'style':'sh_comment'}],[{'exit':true,'regex':/(?:\/)?>/g,'style':'sh_keyword'},{'regex':/[^=" \t>]+/g,'style':'sh_type'},{'regex':/=/g,'style':'sh_symbol'},{'next':13,'regex':/"/g,'style':'sh_string'}],[{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/\*\//g,'style':'sh_comment'},{'next':14,'regex':/\/\*/g,'style':'sh_comment'},{'regex':/(?:<?)[A-Za-z0-9_\.\/\-_]+@[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:<?)[A-Za-z0-9_]+:\/\/[A-Za-z0-9_\.\/\-_]+(?:>?)/g,'style':'sh_url'},{'regex':/(?:TODO|FIXME)(?:[:]?)/g,'style':'sh_todo'}],[{'exit':true,'regex':/"/g,'style':'sh_string'},{'regex':/\\./g,'style':'sh_specialchar'}],[{'exit':true,'regex':/'/g,'style':'sh_string'},{'regex':/\\./g,'style':'sh_specialchar'}]];
553 if(!this.sh_languages){this.sh_languages={};}
554 sh_languages['ruby']=[[{'regex':/\b(?:require)\b/g,'style':'sh_preproc'},{'next':1,'regex':/#/g,'style':'sh_comment'},{'regex':/\b[+-]?(?:(?:0x[A-Fa-f0-9]+)|(?:(?:[\d]*\.)?[\d]+(?:[eE][+-]?[\d]+)?))u?(?:(?:int(?:8|16|32|64))|L)?\b/g,'style':'sh_number'},{'next':2,'regex':/"/g,'style':'sh_string'},{'next':3,'regex':/'/g,'style':'sh_string'},{'next':4,'regex':/</g,'style':'sh_string'},{'regex':/\/[^\n]*\//g,'style':'sh_regexp'},{'regex':/(%r)(\{(?:\\\}|#\{[A-Za-z0-9]+\}|[^}])*\})/g,'style':['sh_symbol','sh_regexp']},{'regex':/\b(?:alias|begin|BEGIN|break|case|defined|do|else|elsif|end|END|ensure|for|if|in|include|loop|next|raise|redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield|false|nil|self|true|__FILE__|__LINE__|and|not|or|def|class|module|catch|fail|load|throw)\b/g,'style':'sh_keyword'},{'next':5,'regex':/(?:^\=begin)/g,'style':'sh_comment'},{'regex':/(?:\$[#]?|@@|@)(?:[A-Za-z0-9_]+|'|\"|\/)/g,'style':'sh_type'},{'regex':/[A-Za-z0-9]+(?:\?|!)/g,'style':'sh_normal'},{'regex':/~|!|%|\^|\*|\(|\)|-|\+|=|\[|\]|\\|:|;|,|\.|\/|\?|&|<|>|\|/g,'style':'sh_symbol'},{'regex':/(#)(\{)/g,'style':['sh_symbol','sh_cbracket']},{'regex':/\{|\}/g,'style':'sh_cbracket'}],[{'exit':true,'regex':/$/g}],[{'exit':true,'regex':/$/g},{'regex':/\\(?:\\|")/g},{'exit':true,'regex':/"/g,'style':'sh_string'}],[{'exit':true,'regex':/$/g},{'regex':/\\(?:\\|')/g},{'exit':true,'regex':/'/g,'style':'sh_string'}],[{'exit':true,'regex':/$/g},{'exit':true,'regex':/>/g,'style':'sh_string'}],[{'exit':true,'regex':/^(?:\=end)/g,'style':'sh_comment'}]];