[book] add generated js documentation to cw book
authorSandrine Ribeau <sandrine.ribeau@logilab.fr>
Wed, 05 May 2010 16:44:02 +0200
changeset 5470 fb004819cab4
parent 5468 7199fddc0a88
child 5471 b7bf1c6751fd
[book] add generated js documentation to cw book
doc/book/en/annexes/docstrings-conventions.rst
doc/book/en/annexes/index.rst
doc/book/en/devweb/js.rst
doc/book/en/makefile
doc/tools/pyjsrest.py
web/data/cubicweb.timeline-bundle.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/docstrings-conventions.rst	Wed May 05 16:44:02 2010 +0200
@@ -0,0 +1,106 @@
+Javascript docstrings
+=====================
+
+Whereas in Python source code we only need to include a module docstrings 
+using the directive `.. automodule:: mypythonmodule`, we will have to 
+explicitely define Javascript modules and functions in the doctrings since
+there is no native directive to include Javascript files.
+
+Rest generation
+---------------
+
+`pyjsrest` is a small utility parsing Javascript doctrings and generating the
+corresponding Restructured file used by Sphinx to generate HTML documentation.
+This script will have the following structure::
+
+  ===========
+  filename.js
+  ===========
+  .. module:: filename.js
+
+We use the `.. module::` directive to register a javascript library
+as a Python module for Sphinx. This provides an entry in the module index.
+
+The contents of the docstring found in the javascript file will be added as is
+following the module declaration. No treatment will be done on the doctring.
+All the documentation structure will be in the docstrings and will comply
+with the following rules.
+
+Docstring structure
+-------------------
+
+Basically we document javascript with RestructuredText docstring
+following the same convention as documenting Python code.
+
+The doctring in Javascript files must be contained in standard 
+Javascript comment signs, starting with `/**` and ending with `*/`,
+such as::
+
+ /**
+  * My comment starts here.
+  * This is the second line prefixed with a `*`.
+  * ...
+  * ...
+  * All the follwing line will be prefixed with a `*` followed by a space.
+  * ...
+  * ...
+  */ 
+
+
+Comments line prefixed by `//` will be ignored. They are reserved for source
+code comments dedicated to developers.
+
+
+Javscript functions docstring
+-----------------------------
+
+By default, the `function` directive describes a module-level function.
+
+`function` directive
+~~~~~~~~~~~~~~~~~~~~
+
+Its purpose is to define the function prototype such as::
+
+    .. function:: loadxhtml(url, data, reqtype, mode) 
+
+If any namespace is used, we should add it in the prototype for now, 
+until we define an appropriate directive.
+::
+    .. function:: jQuery.fn.loadxhtml(url, data, reqtype, mode) 
+
+Function parameters
+~~~~~~~~~~~~~~~~~~~
+
+We will define function parameters as a bulleted list, where the
+parameter name will be backquoted and followed by its description.
+
+Example of a javascript function docstring::
+
+    .. function:: loadxhtml(url, data, reqtype, mode) 
+
+    cubicweb loadxhtml plugin to make jquery handle xhtml response
+
+    fetches `url` and replaces this's content with the result
+
+    Its arguments are:
+
+    * `url`
+
+    * `mode`, how the replacement should be done (default is 'replace')
+       Possible values are :
+           - 'replace' to replace the node's content with the generated HTML
+           - 'swap' to replace the node itself with the generated HTML
+           - 'append' to append the generated HTML to the node's content
+
+
+Optional parameter specification
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Javascript functions handle arguments not listed in the function signature.
+In the javascript code, they will be flagged using `/* ... */`. In the docstring,
+we flag those optional arguments the same way we would define it in
+Python::
+
+    .. function:: asyncRemoteExec(fname, arg1=None, arg2=None)
+
+
--- a/doc/book/en/annexes/index.rst	Wed May 05 11:19:58 2010 +0200
+++ b/doc/book/en/annexes/index.rst	Wed May 05 16:44:02 2010 +0200
@@ -17,6 +17,8 @@
    rql/index
    mercurial
    depends
+   javascript-api
+   docstrings-conventions
 
 (X)HTML tricks to apply
 -----------------------
--- a/doc/book/en/devweb/js.rst	Wed May 05 11:19:58 2010 +0200
+++ b/doc/book/en/devweb/js.rst	Wed May 05 16:44:02 2010 +0200
@@ -353,3 +353,11 @@
 There is also javascript support for massmailing, gmap (google maps),
 fckcwconfig (fck editor), timeline, calendar, goa (CubicWeb over
 AppEngine), flot (charts drawing), tabs and bookmarks.
+
+API
+~~~
+
+.. toctree::
+    :maxdepth: 1
+    
+    js_api/index
--- a/doc/book/en/makefile	Wed May 05 11:19:58 2010 +0200
+++ b/doc/book/en/makefile	Wed May 05 16:44:02 2010 +0200
@@ -11,6 +11,10 @@
 PAPER         =
 #BUILDDIR      = build
 BUILDDIR      = ~/tmp/cwdoc
+CWDIR         = ../../..
+JSDIR         = ${CWDIR}/web/data
+JSTORST       = ${CWDIR}/doc/tools/pyjsrest.py
+BUILDJS       = devweb/js_api
 
 # Internal variables for sphinx
 PAPEROPT_a4     = -D latex_paper_size=a4
@@ -18,6 +22,7 @@
 ALLSPHINXOPTS   = -d ${BUILDDIR}/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 
 
+
 .PHONY: help clean html web pickle htmlhelp latex changes linkcheck
 
 help:
@@ -36,6 +41,7 @@
 	rm -rf apidoc/
 	rm -f *.html
 	-rm -rf ${BUILDDIR}/*
+	-rm -rf ${BUILDJS}
 
 all: ${TARGET} apidoc html
 
@@ -48,12 +54,16 @@
 	epydoc --html -o apidoc -n "cubicweb" --exclude=setup --exclude=__pkginfo__ ../../../
 
 # run sphinx ###
-html:
+html: js
 	mkdir -p ${BUILDDIR}/html ${BUILDDIR}/doctrees
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) ${BUILDDIR}/html
 	@echo
 	@echo "Build finished. The HTML pages are in ${BUILDDIR}/html."
 
+js:
+	mkdir -p ${BUILDJS}
+	$(JSTORST) -p ${JSDIR} -o ${BUILDJS}
+
 pickle:
 	mkdir -p ${BUILDDIR}/pickle ${BUILDDIR}/doctrees
 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) ${BUILDDIR}/pickle
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/tools/pyjsrest.py	Wed May 05 16:44:02 2010 +0200
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+"""
+Parser for Javascript comments.
+"""
+from __future__ import with_statement
+
+import sys, os, getopt, re
+
+def clean_comment(match):
+    comment = match.group()
+    comment = strip_stars(comment)
+    return comment
+
+# Rest utilities
+def rest_title(title, level, level_markups=['=', '=', '-', '~', '+', '`']):
+    size = len(title)
+    if level == 0:
+        return '\n'.join((level_markups[level] * size, title, level_markups[0] * size)) + '\n'
+    return '\n'.join(('\n' + title, level_markups[level] * size)) + '\n'
+
+def get_doc_comments(text):
+    """
+    Return a list of all documentation comments in the file text.  Each
+    comment is a pair, with the first element being the comment text and
+    the second element being the line after it, which may be needed to
+    guess function & arguments.
+
+    >>> get_doc_comments(read_file('examples/module.js'))[0][0][:40]
+    '/**\n * This is the module documentation.'
+    >>> get_doc_comments(read_file('examples/module.js'))[1][0][7:50]
+    'This is documentation for the first method.'
+    >>> get_doc_comments(read_file('examples/module.js'))[1][1]
+    'function the_first_function(arg1, arg2) '
+    >>> get_doc_comments(read_file('examples/module.js'))[2][0]
+    '/** This is the documentation for the second function. */'
+
+    """
+    return [clean_comment(match) for match in re.finditer('/\*\*.*?\*/',
+            text, re.DOTALL|re.MULTILINE)]
+
+RE_STARS = re.compile('^\s*?\* ?', re.MULTILINE)
+
+
+def strip_stars(doc_comment):
+    """
+    Strip leading stars from a doc comment.
+
+    >>> strip_stars('/** This is a comment. */')
+    'This is a comment.'
+    >>> strip_stars('/**\n * This is a\n * multiline comment. */')
+    'This is a\n multiline comment.'
+    >>> strip_stars('/** \n\t * This is a\n\t * multiline comment. \n*/')
+    'This is a\n multiline comment.'
+
+    """
+    return RE_STARS.sub('', doc_comment[3:-2]).strip()
+
+def parse_js_files(args=sys.argv):
+    """
+    Main command-line invocation.
+    """
+    try:
+        opts, args = getopt.gnu_getopt(args[1:], 'p:o:h', [
+            'jspath=', 'output=', 'help'])
+        opts = dict(opts)
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+
+    rst_dir = opts.get('--output') or opts.get('-o')
+    if rst_dir is None and len(args) != 1:
+        rst_dir = 'apidocs'
+    js_dir = opts.get('--jspath') or opts.get('-p')
+    if not os.path.exists(os.path.join(rst_dir)):
+        os.makedirs(os.path.join(rst_dir))
+
+    f_index = open(os.path.join(rst_dir, 'index.rst'), 'wb')
+    f_index.write('''
+.. toctree::
+    :maxdepth: 1
+
+'''
+)
+    for js_path, js_dirs, js_files in os.walk(js_dir):
+        rst_path = re.sub('%s%s*' % (js_dir, os.path.sep), '', js_path)
+        for js_file in js_files:
+            if not js_file.endswith('.js'):
+                continue
+            if not os.path.exists(os.path.join(rst_dir, rst_path)):
+                os.makedirs(os.path.join(rst_dir, rst_path))
+            rst_content =  extract_rest(js_path, js_file)
+            filename = os.path.join(rst_path, js_file[:-3])
+            # add to index
+            f_index.write('    %s\n' % filename)
+            # save rst file
+            with open(os.path.join(rst_dir, filename) + '.rst', 'wb') as f_rst:
+                f_rst.write(rst_content)
+    f_index.close()
+
+def extract_rest(js_dir, js_file):
+    js_filepath = os.path.join(js_dir, js_file)
+    filecontent = open(js_filepath, 'U').read()
+    comments = get_doc_comments(filecontent)
+    rst = rest_title(js_file, 0)
+    rst += '.. module:: %s\n\n' % js_file
+    rst += '\n\n'.join(comments)
+    return rst
+
+if __name__ == '__main__':
+    parse_js_files()
--- a/web/data/cubicweb.timeline-bundle.js	Wed May 05 11:19:58 2010 +0200
+++ b/web/data/cubicweb.timeline-bundle.js	Wed May 05 16:44:02 2010 +0200
@@ -1,14 +1,14 @@
 var SimileAjax_urlPrefix = baseuri() + 'data/';
 var Timeline_urlPrefix = baseuri() + 'data/';
 
-/*==================================================
+/*
  *  Simile Ajax API
  *
  *  Include this file in your HTML file as follows:
  *
  *    <script src="http://simile.mit.edu/ajax/api/simile-ajax-api.js" type="text/javascript"></script>
  *
- *==================================================
+ *
  */
 
 if (typeof SimileAjax == "undefined") {
@@ -213,9 +213,9 @@
         SimileAjax.loaded = true;
     })();
 }
-/*==================================================
+/*
  *  Platform Utility Functions and Constants
- *==================================================
+ *
  */
 
 /*  This must be called after our jQuery has been loaded
@@ -319,9 +319,10 @@
 
 SimileAjax.Platform.getDefaultLocale = function() {
     return SimileAjax.Platform.clientLocale;
-};/*==================================================
+};
+/*
  *  Debug Utility Functions
- *==================================================
+ *
  */
 
 SimileAjax.Debug = {
@@ -678,9 +679,9 @@
         }
     };
 })();
-/*==================================================
+/*
  *  DOM Utility Functions
- *==================================================
+ *
  */
 
 SimileAjax.DOM = new Object();
@@ -1040,9 +1041,9 @@
     SimileAjax.includeCssFile(document, SimileAjax.urlPrefix + "styles/graphics-ie6.css");
 }
 
-/*==================================================
+/*
  *  Opacity, translucency
- *==================================================
+ *
  */
 SimileAjax.Graphics._createTranslucentImage1 = function(url, verticalAlign) {
     var elmt = document.createElement("img");
@@ -1119,9 +1120,9 @@
     }
 };
 
-/*==================================================
+/*
  *  Bubble
- *==================================================
+ *
  */
 
 SimileAjax.Graphics.bubbleConfig = {
@@ -1479,9 +1480,9 @@
     };
 };
 
-/*==================================================
+/*
  *  Animation
- *==================================================
+ *
  */
 
 /**
@@ -1549,11 +1550,11 @@
     }
 };
 
-/*==================================================
+/*
  *  CopyPasteButton
  *
  *  Adapted from http://spaces.live.com/editorial/rayozzie/demo/liveclip/liveclipsample/techPreview.html.
- *==================================================
+ *
  */
 
 /**
@@ -1606,9 +1607,9 @@
     return div;
 };
 
-/*==================================================
+/*
  *  getWidthHeight
- *==================================================
+ *
  */
 SimileAjax.Graphics.getWidthHeight = function(el) {
     // RETURNS hash {width:  w, height: h} in pixels
@@ -1633,9 +1634,9 @@
 };
 
 
-/*==================================================
+/*
  *  FontRenderingContext
- *==================================================
+ *
  */
 SimileAjax.Graphics.getFontRenderingContext = function(elmt, width) {
     return new SimileAjax.Graphics._FontRenderingContext(elmt, width);
@@ -2127,9 +2128,9 @@
     var d = new Date().getTimezoneOffset();
     return d / -60;
 };
-/*==================================================
+/*
  *  String Utility Functions and Constants
- *==================================================
+ *
  */
 
 String.prototype.trim = function() {
@@ -2170,9 +2171,9 @@
     }
     return result;
 };
-/*==================================================
+/*
  *  HTML Utility Functions
- *==================================================
+ *
  */
 
 SimileAjax.HTML = new Object();
@@ -2655,9 +2656,9 @@
     return (this._a.length > 0) ? this._a[this._a.length - 1] : null;
 };
 
-/*==================================================
+/*
  *  Event Index
- *==================================================
+ *
  */
 
 SimileAjax.EventIndex = function(unit) {
@@ -2889,9 +2890,9 @@
         return this._index < this._events.length() ?
             this._events.elementAt(this._index++) : null;
     }
-};/*==================================================
+};/*
  *  Default Unit
- *==================================================
+ *
  */
 
 SimileAjax.NativeDateUnit = new Object();
@@ -2953,9 +2954,9 @@
     return new Date(v.getTime() + n);
 };
 
-/*==================================================
+/*
  *  General, miscellaneous SimileAjax stuff
- *==================================================
+ *
  */
 
 SimileAjax.ListenerQueue = function(wildcardHandlerName) {
@@ -2998,7 +2999,7 @@
     }
 };
 
-/*======================================================================
+/*
  *  History
  *
  *  This is a singleton that keeps track of undoable user actions and
@@ -3020,7 +3021,7 @@
  *
  *  An iframe is inserted into the document's body element to track
  *  onload events.
- *======================================================================
+ *
  */
 
 SimileAjax.History = {
@@ -3632,7 +3633,7 @@
     }
     return elmt;
 };
-/*==================================================
+/*
  *  Timeline API
  *
  *  This file will load all the Javascript files
@@ -3696,7 +3697,7 @@
  * Note that the Ajax version is usually NOT the same as the Timeline version.
  * See variable simile_ajax_ver below for the current version
  *
- *==================================================
+ *
  */
 
 (function() {
@@ -3928,7 +3929,7 @@
         loadMe();
     }
 })();
-/*=================================================
+/*
  *
  * Coding standards:
  *
@@ -3950,14 +3951,14 @@
  * We also want to use jslint:  http://www.jslint.com/
  *
  *
- *==================================================
+ *
  */
 
 
 
-/*==================================================
+/*
  *  Timeline VERSION
- *==================================================
+ *
  */
 // Note: version is also stored in the build.xml file
 Timeline.version = 'pre 2.4.0';  // use format 'pre 1.2.3' for trunk versions
@@ -3965,9 +3966,9 @@
 Timeline.display_version = Timeline.version + ' (with Ajax lib ' + Timeline.ajax_lib_version + ')';
  // cf method Timeline.writeVersion
 
-/*==================================================
+/*
  *  Timeline
- *==================================================
+ *
  */
 Timeline.strings = {}; // localization string tables
 Timeline.HORIZONTAL = 0;
@@ -4183,9 +4184,9 @@
 
 
 
-/*==================================================
+/*
  *  Timeline Implementation object
- *==================================================
+ *
  */
 Timeline._Impl = function(elmt, bandInfos, orientation, unit, timelineID) {
     SimileAjax.WindowManager.initialize();
@@ -4585,7 +4586,7 @@
   this.paint();
 };
 
-/*=================================================
+/*
  *
  * Coding standards:
  *
@@ -4607,14 +4608,14 @@
  * We also want to use jslint:  http://www.jslint.com/
  *
  *
- *==================================================
+ *
  */
 
 
 
-/*==================================================
+/*
  *  Band
- *==================================================
+ *
  */
 Timeline._Band = function(timeline, bandInfo, index) {
     // hack for easier subclassing
@@ -5344,9 +5345,9 @@
 Timeline._Band.prototype.closeBubble = function() {
     SimileAjax.WindowManager.cancelPopups();
 };
-/*==================================================
+/*
  *  Classic Theme
- *==================================================
+ *
  */
 
 
@@ -5523,14 +5524,14 @@
     };
 
     this.mouseWheel = 'scroll'; // 'default', 'zoom', 'scroll'
-};/*==================================================
+};/*
  *  An "ether" is a object that maps date/time to pixel coordinates.
- *==================================================
+ *
  */
 
-/*==================================================
+/*
  *  Linear Ether
- *==================================================
+ *
  */
 
 Timeline.LinearEther = function(params) {
@@ -5601,9 +5602,9 @@
 };
 
 
-/*==================================================
+/*
  *  Hot Zone Ether
- *==================================================
+ *
  */
 
 Timeline.HotZoneEther = function(params) {
@@ -5828,9 +5829,9 @@
 Timeline.HotZoneEther.prototype._getScale = function() {
     return this._interval / this._pixelsPerInterval;
 };
-/*==================================================
+/*
  *  Gregorian Ether Painter
- *==================================================
+ *
  */
 
 Timeline.GregorianEtherPainter = function(params) {
@@ -5919,9 +5920,9 @@
 };
 
 
-/*==================================================
+/*
  *  Hot Zone Gregorian Ether Painter
- *==================================================
+ *
  */
 
 Timeline.HotZoneGregorianEtherPainter = function(params) {
@@ -6080,9 +6081,9 @@
   }
 };
 
-/*==================================================
+/*
  *  Year Count Ether Painter
- *==================================================
+ *
  */
 
 Timeline.YearCountEtherPainter = function(params) {
@@ -6169,9 +6170,9 @@
 Timeline.YearCountEtherPainter.prototype.softPaint = function() {
 };
 
-/*==================================================
+/*
  *  Quarterly Ether Painter
- *==================================================
+ *
  */
 
 Timeline.QuarterlyEtherPainter = function(params) {
@@ -6257,9 +6258,9 @@
 Timeline.QuarterlyEtherPainter.prototype.softPaint = function() {
 };
 
-/*==================================================
+/*
  *  Ether Interval Marker Layout
- *==================================================
+ *
  */
 
 Timeline.EtherIntervalMarkerLayout = function(timeline, band, theme, align, showLine) {
@@ -6363,9 +6364,9 @@
     };
 };
 
-/*==================================================
+/*
  *  Ether Highlight Layout
- *==================================================
+ *
  */
 
 Timeline.EtherHighlight = function(timeline, band, theme, backgroundLayer) {
@@ -6404,9 +6405,9 @@
         }
     }
 };
-/*==================================================
+/*
  *  Event Utils
- *==================================================
+ *
  */
 Timeline.EventUtils = {};
 
@@ -6421,7 +6422,7 @@
 };
 
 Timeline.EventUtils.decodeEventElID = function(elementID) {
-    /*==================================================
+    /*
      *
      * Use this function to decode an event element's id on a band (label div,
      * tape div or icon img).
@@ -6447,7 +6448,7 @@
      * by using Timeline.getTimeline, Timeline.getBand, or
      * Timeline.getEvent and passing in the element's id
      *
-     *==================================================
+     *
      */
 
     var parts = elementID.split('-');
@@ -6467,9 +6468,9 @@
     // elType should be one of {label | icon | tapeN | highlightN}
     return elType + "-tl-" + timeline.timelineID +
        "-" + band.getIndex() + "-" + evt.getID();
-};/*==================================================
+};/*
  *  Gregorian Date Labeller
- *==================================================
+ *
  */
 
 Timeline.GregorianDateLabeller = function(locale, timeZone) {
@@ -6558,9 +6559,9 @@
     return { text: text, emphasized: emphasized };
 }
 
-/*==================================================
+/*
  *  Default Event Source
- *==================================================
+ *
  */
 
 
@@ -7125,12 +7126,12 @@
 };
 
 
-/*==================================================
+/*
  *  Original Event Painter
- *==================================================
+ *
  */
 
-/*==================================================
+/*
  *
  * To enable a single event listener to monitor everything
  * on a Timeline, we need a way to map from an event's icon,
@@ -7152,7 +7153,7 @@
  * You can then retrieve the band/timeline objects and event object
  * by using Timeline.EventUtils.decodeEventElID
  *
- *==================================================
+ *
  */
 
 /*
@@ -7818,9 +7819,9 @@
         this._eventPaintListeners[i](this._band, op, evt, els);
     }
 };
-/*==================================================
+/*
  *  Detailed Event Painter
- *==================================================
+ *
  */
 
 // Note: a number of features from original-painter
@@ -8509,9 +8510,9 @@
         this._onSelectListeners[i](eventID);
     }
 };
-/*==================================================
+/*
  *  Overview Event Painter
- *==================================================
+ *
  */
 
 Timeline.OverviewEventPainter = function(params) {
@@ -8767,9 +8768,9 @@
 Timeline.OverviewEventPainter.prototype.showBubble = function(evt) {
     // not implemented
 };
-/*==================================================
+/*
  *  Compact Event Painter
- *==================================================
+ *
  */
 
 Timeline.CompactEventPainter = function(params) {
@@ -9831,9 +9832,9 @@
         this._onSelectListeners[i](eventIDs);
     }
 };
-/*==================================================
+/*
  *  Span Highlight Decorator
- *==================================================
+ *
  */
 
 Timeline.SpanHighlightDecorator = function(params) {
@@ -9948,9 +9949,9 @@
 Timeline.SpanHighlightDecorator.prototype.softPaint = function() {
 };
 
-/*==================================================
+/*
  *  Point Highlight Decorator
- *==================================================
+ *
  */
 
 Timeline.PointHighlightDecorator = function(params) {
@@ -10015,9 +10016,9 @@
 
 Timeline.PointHighlightDecorator.prototype.softPaint = function() {
 };
-/*==================================================
+/*
  *  Default Unit
- *==================================================
+ *
  */
 
 Timeline.NativeDateUnit = new Object();
@@ -10083,35 +10084,35 @@
     return new Date(v.getTime() + n);
 };
 
-/*==================================================
+/*
  *  Common localization strings
- *==================================================
+ *
  */
 
 Timeline.strings["fr"] = {
     wikiLinkLabel:  "Discute"
 };
 
-/*==================================================
+/*
  *  Localization of labellers.js
- *==================================================
+ *
  */
 
 Timeline.GregorianDateLabeller.monthNames["fr"] = [
     "jan", "fev", "mar", "avr", "mai", "jui", "jui", "aou", "sep", "oct", "nov", "dec"
 ];
-/*==================================================
+/*
  *  Common localization strings
- *==================================================
+ *
  */
 
 Timeline.strings["en"] = {
     wikiLinkLabel:  "Discuss"
 };
 
-/*==================================================
+/*
  *  Localization of labellers.js
- *==================================================
+ *
  */
 
 Timeline.GregorianDateLabeller.monthNames["en"] = [