5 classvar currentClass, currentImplClass, currentMethod, currArg;
13 *escapeSpecialChars {|str|
15 var beg = -1, end = 0;
18 $&, { x = x ++ str.copyRange(beg, i-1) ++ "&"; beg = i+1; },
19 $<, { x = x ++ str.copyRange(beg, i-1) ++ "<"; beg = i+1; },
20 $>, { x = x ++ str.copyRange(beg, i-1) ++ ">"; beg = i+1; },
25 x = x ++ str[beg..end];
32 // FIXME: how slow is this? can we optimize
33 #n, m, f = link.split($#); // link, anchor, label
34 ^if ("^[a-zA-Z]+://.+".matchRegexp(link) or: (link.first==$/)) {
35 if(f.size<1) {f=link};
36 c = if(m.size>0) {n++"#"++m} {n};
37 "<a href=\""++c++"\">"++this.escapeSpecialChars(f)++"</a>";
41 doc = SCDoc.documents[n];
42 // link to other doc (might not be rendered yet)
46 // link to ready-made html (Search, Browse, etc)
47 if(File.exists(SCDoc.helpTargetDir+/+n++".html")) {
50 // link to other file?
51 if(File.exists(SCDoc.helpTargetDir+/+n).not) {
54 .format(currDoc.fullPath, link).warn;
59 c = ""; // link inside same document
61 if(m.size>0) { c = c ++ "#" ++ m }; // add #anchor
62 if(f.size<1) { // no label
64 f = if(doc.notNil) {doc.title} {n.basename};
69 f = if(m.size>0) {m} {"(empty link)"};
72 "<a href=\""++c++"\">"++this.escapeSpecialChars(f)++"</a>";
76 *makeArgString {|m, par=true|
82 if (i>0) { //skip 'this' (first arg)
83 if(i==last and: {m.varArgs}) {
84 res = res ++ " <span class='argstr'>" ++ "... " ++ a;
86 if (i>1) { res = res ++ ", " };
87 res = res ++ "<span class='argstr'>" ++ a;
88 (value = m.prototypeFrame[i]) !? {
89 value = if(value.class===Float) { value.asString } { value.cs };
90 res = res ++ ": " ++ value;
93 res = res ++ "</span>";
96 if (res.notEmpty and: par) {
102 *renderHeader {|stream, doc|
104 var folder = doc.path.dirname;
105 var undocumented = false;
106 if(folder==".",{folder=""});
109 doc.path.occurrencesOf(Platform.pathSeparator).do {
110 baseDir = baseDir ++ "/..";
114 << "<html><head><title>" << doc.title << "</title>\n"
115 << "<link rel='stylesheet' href='" << baseDir << "/scdoc.css' type='text/css' />\n"
116 << "<link rel='stylesheet' href='" << baseDir << "/frontend.css' type='text/css' />\n"
117 << "<link rel='stylesheet' href='" << baseDir << "/custom.css' type='text/css' />\n"
118 << "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n"
119 << "<script src='" << baseDir << "/scdoc.js' type='text/javascript'></script>\n"
120 << "<script src='" << baseDir << "/docmap.js' type='text/javascript'></script>\n" // FIXME: remove?
121 << "<script src='" << baseDir << "/prettify.js' type='text/javascript'></script>\n"
122 << "<script src='" << baseDir << "/lang-sc.js' type='text/javascript'></script>\n"
123 << "<script src='" << baseDir << "/MathJax/MathJax.js?config=TeX-AMS_HTML,scmathjax' type='text/javascript'></script>\n"
124 << "<script type='text/javascript'>var helpRoot='" << baseDir << "';</script>\n"
128 << "<ul id='menubar'></ul>\n"
129 << "<body onload='fixTOC();prettyPrint()'>\n"
130 << "<div class='contents'>\n"
131 << "<div class='header'>\n"
132 << "<div id='label'>SuperCollider " << folder.asString.toUpper;
133 if(doc.isExtension) {
134 stream << " (extension)";
136 stream << "</div>\n";
140 << "<div id='categories'>"
141 << (doc.categories.collect {|r|
142 "<a href='"++baseDir +/+ "Browse.html#"++r++"'>"++r++"</a>"
147 stream << "<h1>" << doc.title;
148 if((folder=="") and: {doc.title=="Help"}) {
149 stream << "<span class='headerimage'><img src='" << baseDir << "/images/SC_icon.png'/></span>";
153 << "<div id='summary'>" << this.escapeSpecialChars(doc.summary) << "</div>\n"
155 << "<div class='subheader'>\n";
158 if(currentClass.notNil) {
159 m = currentClass.filenameSymbol.asString;
160 stream << "<div id='filename'>Source: "
161 << m.dirname << "/<a href='file://" << m << "'>" << m.basename << "</a></div>";
162 if(currentClass != Object) {
163 stream << "<div id='superclasses'>"
165 << (currentClass.superclasses.collect {|c|
166 "<a href=\"../Classes/"++c.name++".html\">"++c.name++"</a>"
170 if(currentClass.subclasses.notNil) {
172 stream << "<div id='subclasses'>"
174 << (currentClass.subclasses.collect(_.name).sort.collect {|c,i|
175 if(i==12,{z=true;"<span id='hiddensubclasses' style='display:none;'>"},{""})
176 ++"<a href=\"../Classes/"++c++".html\">"++c++"</a>"
179 stream << "</span><a class='subclass_toggle' href='#' onclick='javascript:showAllSubclasses(this); return false'>… see all</a>";
181 stream << "</div>\n";
183 if(currentImplClass.notNil) {
184 stream << "<div class='inheritance'>Implementing class: "
185 << "<a href=\"../Classes/" << currentImplClass.name << ".html\">"
186 << currentImplClass.name << "</a></div>\n";
189 stream << "<div id='filename'>Location: <b>NOT INSTALLED!</b></div>\n";
194 stream << "<div id='related'>See also: "
195 << (doc.related.collect {|r| this.htmlForLink(r)}.join(", "))
199 // FIXME: Remove this when conversion to new help system is done!
200 if(doc.isUndocumentedClass) {
201 x = Help.findHelpFile(name);
203 stream << ("[ <a href='" ++ baseDir ++ "/OldHelpWrapper.html#"
204 ++x++"?"++SCDoc.helpTargetDir +/+ doc.path ++ ".html"
205 ++"'>old help</a> ]")
209 stream << "</div>\n";
212 *renderChildren {|stream, node|
213 node.children.do {|child| this.renderSubTree(stream, child) };
216 *renderMethod {|stream, node, cls, icls, css, pfx|
217 var args = node.text ?? ""; // only outside class/instance methods
218 var names = node.children[0].children.collect(_.text);
219 var mstat, sym, m, m2, mname2;
222 var methArgsMismatch = false;
226 mname2 = this.escapeSpecialChars(mname);
229 sym = mname.asSymbol;
230 //check for normal method or getter
231 m = icls !? {icls.findRespondingMethodFor(sym.asGetter)};
232 m = m ?? {cls.findRespondingMethodFor(sym.asGetter)};
235 args = this.makeArgString(m);
236 args2 = m.argNames !? {m.argNames[1..]};
239 m2 = icls !? {icls.findRespondingMethodFor(sym.asSetter)};
240 m2 = m2 ?? {cls.findRespondingMethodFor(sym.asSetter)};
243 args = m2.argNames !? {this.makeArgString(m2,false)} ?? {"value"};
244 args2 = m2.argNames !? {m2.argNames[1..]};
249 if(a!=b and: {a!=nil} and: {b!=nil}) {
250 methArgsMismatch = true;
255 {args2.size>maxargs} {
256 maxargs = args2.size;
257 currentMethod = m2 ?? m;
259 {args2.size<minArgs} {
260 minArgs = args2.size;
270 stream << "<h3 class='" << css << "'>"
271 << "<span class='methprefix'>" << (pfx??" ") << "</span>"
272 << "<a name='" << (pfx??".") << mname << "' href='"
273 << baseDir << "/Overviews/Methods.html#"
274 << mname2 << "'>" << mname2 << "</a>"
280 1, { stream << " " << args << "</h3>\n"; },
282 3, { stream << "</h3>\n"; },
286 " Method %% not found.".format(currDoc.fullPath,pfx,mname2).warn;
287 stream << ": METHOD NOT FOUND!</h3>\n";
295 stream << " = " << args << "</h3>\n";
297 stream << "_ (" << args << ")</h3>\n";
303 if(m.isExtensionOf(cls) and: {icls.isNil or: {m.isExtensionOf(icls)}}) {
304 stream << "<div class='extmethod'>From extension in <a href='"
305 << m.filenameSymbol << "'>" << m.filenameSymbol << "</a></div>\n";
307 if(m.ownerClass == icls) {
308 stream << "<div class='supmethod'>From implementing class</div>\n";
310 if(m.ownerClass != cls) {
311 m = m.ownerClass.name;
312 m = if(m.isMetaClassName) {m.asString.drop(5)} {m};
313 stream << "<div class='supmethod'>From superclass: <a href='"
314 << baseDir << "/Classes/" << m << ".html'>" << m << "</a></div>\n";
321 if(methArgsMismatch) {
323 " Grouped methods % does not have the same argument signature."
324 .format(currDoc.fullPath, names).warn;
327 // ignore trailing mul add arguments
328 if(currentMethod.notNil) {
329 currentNArgs = currentMethod.argNames.size;
331 and: {currentMethod.argNames[currentNArgs-1] == \add}
332 and: {currentMethod.argNames[currentNArgs-2] == \mul}) {
333 currentNArgs = currentNArgs - 2;
339 if(node.children.size > 1) {
340 stream << "<div class='method'>";
341 this.renderChildren(stream, node.children[1]);
347 *renderSubTree {|stream, node|
356 this.renderChildren(stream, node);
358 \NL, { }, // these shouldn't be here..
359 // Plain text and modal tags
361 stream << this.escapeSpecialChars(node.text);
364 stream << this.htmlForLink(node.text);
367 stream << "<pre class='code prettyprint lang-sc'>"
368 << this.escapeSpecialChars(node.text)
372 stream << "<code class='code prettyprint lang-sc'>"
373 << this.escapeSpecialChars(node.text)
377 stream << "<em>" << this.escapeSpecialChars(node.text) << "</em>";
380 stream << "<pre>" << this.escapeSpecialChars(node.text) << "</pre>";
383 stream << "<code>" << this.escapeSpecialChars(node.text) << "</code>";
386 stream << "<strong>" << this.escapeSpecialChars(node.text) << "</strong>";
389 stream << "<span class='soft'>" << this.escapeSpecialChars(node.text) << "</span>";
392 stream << "<a class='anchor' name='" << node.text << "'> </a>";
395 node.children.do {|child|
396 stream << "<a class='anchor' name='kw_" << child.text << "'> </a>";
399 \MATHBLOCK, { // uses MathJax to typeset TeX math
400 stream << "<span class='math'>\\[\n"
401 << this.escapeSpecialChars(node.text)
405 stream << "<span class='math'>\\("
406 << this.escapeSpecialChars(node.text)
410 f = node.text.split($#);
411 stream << "<div class='image'><img src='" << f[0] << "'/>";
412 f[1] !? { stream << "<br><b>" << f[1] << "</b>" }; // ugly..
413 stream << "</div>\n";
417 stream << "<div class='note'><span class='notelabel'>NOTE:</span> ";
419 this.renderChildren(stream, node);
423 stream << "<div class='warning'><span class='warninglabel'>WARNING:</span> ";
425 this.renderChildren(stream, node);
429 footNotes = footNotes.add(node);
430 stream << "<a class='footnote anchor' name='footnote_org_"
432 << "' href='#footnote_"
439 stream << "<ul class='tree'>";
440 this.renderClassTree(stream, node.text.asSymbol.asClass);
446 this.renderChildren(stream, node);
450 stream << "<ul class='tree'>\n";
451 this.renderChildren(stream, node);
456 this.renderChildren(stream, node);
459 \ITEM, { // for LIST, TREE and NUMBEREDLIST
462 this.renderChildren(stream, node);
467 this.renderChildren(stream, node);
471 this.renderChildren(stream, node);
476 this.renderChildren(stream, node);
481 this.renderChildren(stream, node);
485 stream << "<table>\n";
486 this.renderChildren(stream, node);
487 stream << "</table>\n";
491 this.renderChildren(stream, node);
496 this.renderChildren(stream, node);
502 currentClass !? {currentClass.class},
503 currentImplClass !? {currentImplClass.class},
528 stream << "<h4>Arguments:</h4>\n<table class='arguments'>\n";
530 if(currentMethod.notNil and: {node.children.size < (currentNArgs-1)}) {
532 " Method %% has % args, but doc has % argument:: tags.".format(
534 if(currentMethod.ownerClass.isMetaClass) {"*"} {"-"},
540 this.renderChildren(stream, node);
541 stream << "</table>";
544 currArg = currArg + 1;
545 stream << "<tr><td class='argumentname'>";
546 if(node.text.isNil) {
548 if(currentMethod.varArgs and: {currArg==(currentMethod.argNames.size-1)}) {
551 stream << if(currArg < currentMethod.argNames.size) {
552 if(currArg > minArgs) {
553 "("++currentMethod.argNames[currArg]++")";
555 currentMethod.argNames[currArg];
558 "(arg"++currArg++")" // excessive arg
562 stream << if(currentMethod.isNil or: {currArg < currentMethod.argNames.size}) {
564 f = currentMethod.argNames[currArg].asString;
566 (z = if(currentMethod.varArgs and: {currArg==(currentMethod.argNames.size-1)})
571 " Method %% has arg named '%', but doc has 'argument:: %'.".format(
573 if(currentMethod.ownerClass.isMetaClass) {"*"} {"-"},
580 if(currArg > minArgs) {
586 "("++node.text++")" // excessive arg
589 stream << "<td class='argumentdesc'>";
590 this.renderChildren(stream, node);
593 stream << "<h4>Returns:</h4>\n<div class='returnvalue'>";
594 this.renderChildren(stream, node);
599 stream << "<h4>Discussion:</h4>\n";
600 this.renderChildren(stream, node);
604 if(node.notPrivOnly) {
605 stream << "<h2><a class='anchor' name='classmethods'>Class Methods</a></h2>\n";
607 this.renderChildren(stream, node);
610 if(node.notPrivOnly) {
611 stream << "<h2><a class='anchor' name='instancemethods'>Instance Methods</a></h2>\n";
613 this.renderChildren(stream, node);
616 stream << "<h2><a class='anchor' name='description'>Description</a></h2>\n";
617 this.renderChildren(stream, node);
620 stream << "<h2><a class='anchor' name='examples'>Examples</a></h2>\n";
621 this.renderChildren(stream, node);
624 stream << "<h2><a class='anchor' name='" << node.text
625 << "'>" << this.escapeSpecialChars(node.text) << "</a></h2>\n";
626 if(node.makeDiv.isNil) {
627 this.renderChildren(stream, node);
629 stream << "<div id='" << node.makeDiv << "'>";
630 this.renderChildren(stream, node);
635 stream << "<h3><a class='anchor' name='" << node.text
636 << "'>" << this.escapeSpecialChars(node.text) << "</a></h3>\n";
637 if(node.makeDiv.isNil) {
638 this.renderChildren(stream, node);
640 stream << "<div id='" << node.makeDiv << "'>";
641 this.renderChildren(stream, node);
647 " Unknown SCDocNode id: %".format(currDoc.fullPath, node.id).warn;
648 this.renderChildren(stream, node);
653 *renderTOC {|stream, node|
655 stream << "<ul class='toc'>";
656 node.children.do {|n|
659 stream << "<li class='toc1'><a href='#description'>Description</a></li>\n";
660 this.renderTOC(stream, n);
663 stream << "<li class='toc1'><a href='#examples'>Examples</a></li>\n";
664 this.renderTOC(stream, n);
668 stream << "<li class='toc1'><a href='#classmethods'>Class methods</a></li>\n";
669 this.renderTOC(stream, n);
674 stream << "<li class='toc1'><a href='#instancemethods'>Instance methods</a></li>\n";
675 this.renderTOC(stream, n);
679 stream << "<li class='toc3'>"
680 << (n.children[0].children.collect{|m|
681 "<a href='#*"++m.text++"'>"++this.escapeSpecialChars(m.text)++"</a> ";
686 stream << "<li class='toc3'>"
687 << (n.children[0].children.collect{|m|
688 "<a href='#-"++m.text++"'>"++this.escapeSpecialChars(m.text)++"</a> ";
693 stream << "<li class='toc3'>"
694 << (n.children[0].children.collect{|m|
695 "<a href='#."++m.text++"'>"++this.escapeSpecialChars(m.text)++"</a> ";
701 stream << "<li class='toc1'><a href='#" << n.text << "'>"
702 << this.escapeSpecialChars(n.text) << "</a></li>\n";
703 this.renderTOC(stream, n);
706 stream << "<li class='toc2'><a href='#" << n.text << "'>"
707 << this.escapeSpecialChars(n.text) << "</a></li>\n";
708 this.renderTOC(stream, n);
716 *addUndocumentedMethods {|list, body, id2, id, title|
719 l = list.collectAs(_.asString,Array).sort.collect {|name|
728 .text_(name.asString)
732 body.addDivAfter(id, nil, title, l);
736 *renderClassTree {|stream, cls|
737 var name, doc, desc = "";
738 name = cls.name.asString;
739 doc = SCDoc.documents["Classes/"++name];
740 doc !? { desc = " - "++doc.summary };
741 if(cls.name.isMetaClassName, {^this});
742 stream << "<li> <a href='" << baseDir << "/Classes/" << name << ".html'>"
743 << name << "</a>" << desc << "\n";
746 stream << "<ul class='tree'>\n";
747 cls.subclasses.copy.sort {|a,b| a.name < b.name}.do {|x|
748 this.renderClassTree(stream, x);
754 *renderFootNotes {|stream|
755 if(footNotes.notNil) {
756 stream << "<div class='footnotes'>\n";
758 stream << "<a class='anchor' name='footnote_" << (i+1) << "'/><div class='footnote'>"
759 << "[<a href='#footnote_org_" << (i+1) << "'>" << (i+1) << "</a>] - ";
761 this.renderChildren(stream, n);
768 *renderFooter {|stream, doc|
769 stream << "<div class='doclink'>";
771 stream << "source: <a href='file://"
772 << doc.fullPath << "'>" << doc.fullPath << "</a><br>"
774 stream << "link::" << doc.path << "::<br>"
775 << "sc version: " << Main.version << "</div>"
776 << "</div></body></html>";
779 *renderOnStream {|stream, doc, root|
780 var body = root.children[1];
787 currentClass = doc.klass;
788 currentImplClass = doc.implKlass;
789 if(currentClass != Object) {
790 body.addDivAfter(\CLASSMETHODS,"inheritedclassmets","Inherited class methods");
791 body.addDivAfter(\INSTANCEMETHODS,"inheritedinstmets","Inherited instance methods");
793 this.addUndocumentedMethods(doc.undoccmethods, body, \CMETHOD, \CLASSMETHODS, "Undocumented class methods");
794 this.addUndocumentedMethods(doc.undocimethods, body, \IMETHOD, \INSTANCEMETHODS, "Undocumented instance methods");
798 currentImplClass = nil;
801 this.renderHeader(stream, doc);
802 stream << "<div id='toc'>\n";
803 this.renderTOC(stream, body);
805 this.renderChildren(stream, body);
806 this.renderFootNotes(stream);
807 this.renderFooter(stream, doc);
811 *renderToFile {|filename, doc, root|
813 File.mkdir(filename.dirname);
814 stream = File(filename, "w");
816 this.renderOnStream(stream, doc, root);
819 warn("SCDoc: Could not open file % for writing".format(filename));