diff --git a/doc/_extensions/autojsdoc/__main__.py b/doc/_extensions/autojsdoc/__main__.py index 20ab65c2c37f52d14969e1ef0a13d6a7e4432868..2ac5f3a0cb3fc7c669af9eed766753a52211d6d0 100644 --- a/doc/_extensions/autojsdoc/__main__.py +++ b/doc/_extensions/autojsdoc/__main__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import print_function import cgitb import fnmatch import io @@ -6,13 +7,12 @@ import logging import click -import pyjsdoc import pyjsparser import sys from .parser.parser import ModuleMatcher from .parser.visitor import Visitor, SKIP -from . import jsdoc +from .parser import jsdoc class Printer(Visitor): def __init__(self, level=0): @@ -20,7 +20,7 @@ class Printer(Visitor): self.level = level def _print(self, text): - print ' ' * self.level, text + print(' ' * self.level, text) def enter_generic(self, node): self._print(node['type']) diff --git a/doc/_extensions/autojsdoc/parser/jsdoc.py b/doc/_extensions/autojsdoc/parser/jsdoc.py index 00406df42c132ce2f514d0762da496403c63c162..a34a2aa9b8315d9851fe380d9ffba1d0cf984a4e 100644 --- a/doc/_extensions/autojsdoc/parser/jsdoc.py +++ b/doc/_extensions/autojsdoc/parser/jsdoc.py @@ -174,7 +174,7 @@ class NSDoc(CommentDoc): for p in self.get_as_list('property') ) ] - return self.members.items() or self['_members'] or [] + return list(self.members.items()) or self['_members'] or [] def has_property(self, name): return self.get_property(name) is not None diff --git a/doc/_extensions/autojsdoc/parser/parser.py b/doc/_extensions/autojsdoc/parser/parser.py index d91b9f225960ef32faae91df1c6cbd7ad4d74c86..358d20c8e9b65d8ee05a49bfc984133235c61b1c 100644 --- a/doc/_extensions/autojsdoc/parser/parser.py +++ b/doc/_extensions/autojsdoc/parser/parser.py @@ -2,7 +2,6 @@ import collections import pyjsdoc -from attr import attrs, Factory, attr, astuple from . import jsdoc from . import utils @@ -97,21 +96,25 @@ def m2r(me, scope): utils._value(me['property'], strict=True) ) -@attrs(slots=True) +NOTHING = object() class Declaration(object): - id = attr(default=None) - comments = attr(default=Factory(list)) -@attrs(slots=True) + __slots__ = ['id', 'comments'] + def __init__(self, id=None, comments=NOTHING): + self.id = id + self.comments = [] if comments is NOTHING else comments + class ModuleContent(object): - dependencies = attr(default=Factory(set)) - post = attr(default=Factory(list)) + __slots__ = ['dependencies', 'post'] + def __init__(self, dependencies=NOTHING, post=NOTHING): + self.dependencies = set() if dependencies is NOTHING else dependencies + self.post = [] if post is NOTHING else post def __iter__(self): yield self.dependencies yield self.post -@attrs # needs dict as it's replacing a ModuleProxy class Nothing(object): - name = attr() + def __init__(self, name): + self.name = name def __bool__(self): return False __nonzero__ = __bool__ @@ -389,7 +392,7 @@ class ValueExtractor(Visitor): return SKIP def enter_FunctionExpression(self, node): - name, comments = astuple(self.declaration) + name, comments = (self.declaration.id, self.declaration.comments) self.result = jsdoc.parse_comments(comments, jsdoc.FunctionDoc) self.result.parsed['name'] = node['id'] and node['id']['name'] self._update_result_meta() @@ -437,7 +440,7 @@ class ValueExtractor(Visitor): }, }): # creates a new class, but may not actually return it obj = node['callee']['object'] - _, comments = astuple(self.declaration) + comments = self.declaration.comments self.result = cls = jsdoc.parse_comments(comments, jsdoc.ClassDoc) cls.parsed['extends'] = self.parent.refify(obj) self._update_result_meta() diff --git a/doc/_extensions/autojsdoc/parser/utils.py b/doc/_extensions/autojsdoc/parser/utils.py index b8b1a97e7aa36123b17d0f10bf34c537da9edcb9..213327f6c0e5375f4d495d6468aae6018a8e0a4e 100644 --- a/doc/_extensions/autojsdoc/parser/utils.py +++ b/doc/_extensions/autojsdoc/parser/utils.py @@ -104,7 +104,7 @@ def resolve(obj, store): if obj is None: return None - if isinstance(obj, (unicode, bool, int, float)): + if isinstance(obj, (type(u''), bool, int, float)): return obj if isinstance(obj, ModuleProxy): @@ -139,11 +139,11 @@ class Namespace(Resolvable): r = super(Namespace, self).resolve(store) self.attrs = { k: resolve(v, store) - for k, v in self.attrs.iteritems() + for k, v in self.attrs.items() } return r def __getitem__(self, key): - assert isinstance(key, unicode), "%r is not a namespace key" % key + assert isinstance(key, type(u'')), "%r is not a namespace key" % key try: return self.attrs[key] except KeyError: diff --git a/doc/_extensions/odoo_ext/translator.py b/doc/_extensions/odoo_ext/translator.py index c2349bca315b919d9b0b57d58c09771d43c90081..1c6d1c86d2eb15fa5369997e7fd0beeb659b50af 100644 --- a/doc/_extensions/odoo_ext/translator.py +++ b/doc/_extensions/odoo_ext/translator.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import print_function + import os.path import posixpath import re @@ -114,7 +116,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object): return u''.join(self.body) def unknown_visit(self, node): - print "unknown node", node.__class__.__name__ + print("unknown node", node.__class__.__name__) self.body.append(u'[UNKNOWN NODE {}]'.format(node.__class__.__name__)) raise nodes.SkipNode diff --git a/doc/_extensions/pyjsdoc/LICENSE b/doc/_extensions/pyjsdoc/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..dd5b3a58aa1849f452abc9b5cd1638dc71a5e482 --- /dev/null +++ b/doc/_extensions/pyjsdoc/LICENSE @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/doc/_extensions/pyjsdoc/__init__.py b/doc/_extensions/pyjsdoc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8368f73083ec7572651e41386c7406d090f1910c --- /dev/null +++ b/doc/_extensions/pyjsdoc/__init__.py @@ -0,0 +1,408 @@ +from __future__ import division +import re, json + +##### Parsing utilities ##### + +def split_delimited(delimiters, split_by, text): + """ + Generator that walks the ``text`` and splits it into an array on + ``split_by``, being careful not to break inside a delimiter pair. + ``delimiters`` should be an even-length string with each pair of matching + delimiters listed together, open first. + + + >>> list(split_delimited('{}[]', ',', '')) + [''] + >>> list(split_delimited('', ',', 'foo,bar')) + ['foo', 'bar'] + >>> list(split_delimited('[]', ',', 'foo,[bar, baz]')) + ['foo', '[bar, baz]'] + >>> list(split_delimited('{}', ' ', '{Type Name} name Desc')) + ['{Type Name}', 'name', 'Desc'] + >>> list(split_delimited('[]{}', ',', '[{foo,[bar, baz]}]')) + ['[{foo,[bar, baz]}]'] + + Two adjacent delimiters result in a zero-length string between them: + + >>> list(split_delimited('{}', ' ', '{Type Name} Desc')) + ['{Type Name}', '', 'Desc'] + + ``split_by`` may be a predicate function instead of a string, in which + case it should return true on a character to split. + + >>> list(split_delimited('', lambda c: c in '[]{}, ', '[{foo,[bar, baz]}]')) + ['', '', 'foo', '', 'bar', '', 'baz', '', '', ''] + + """ + delims = [0] * (len(delimiters) // 2) + actions = {} + for i in range(0, len(delimiters), 2): + actions[delimiters[i]] = (i // 2, 1) + actions[delimiters[i + 1]] = (i // 2, -1) + + if isinstance(split_by, str): + def split_fn(c): + return c == split_by + else: + split_fn = split_by + last = 0 + + for i in range(len(text)): + c = text[i] + if split_fn(c) and not any(delims): + yield text[last:i] + last = i + 1 + try: + which, dir = actions[c] + delims[which] = delims[which] + dir + except KeyError: + pass # Normal character + yield text[last:] + + +def strip_stars(doc_comment): + r""" + 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.sub('\n\s*?\*\s*?', '\n', doc_comment[3:-2]).strip() + + +def split_tag(section): + """ + Split the JSDoc tag text (everything following the @) at the first + whitespace. Returns a tuple of (tagname, body). + """ + splitval = re.split('\s+', section, 1) + tag, body = len(splitval) > 1 and splitval or (splitval[0], '') + return tag.strip(), body.strip() + + +FUNCTION_REGEXPS = [ + 'function (\w+)', + '(\w+):\sfunction', + '\.(\w+)\s*=\s*function', +] + + +def guess_function_name(next_line, regexps=FUNCTION_REGEXPS): + """ + Attempt to determine the function name from the first code line + following the comment. The patterns recognized are described by + `regexps`, which defaults to FUNCTION_REGEXPS. If a match is successful, + returns the function name. Otherwise, returns None. + """ + for regexp in regexps: + match = re.search(regexp, next_line) + if match: + return match.group(1) + return None + + +def guess_parameters(next_line): + """ + Attempt to guess parameters based on the presence of a parenthesized + group of identifiers. If successful, returns a list of parameter names; + otherwise, returns None. + """ + match = re.search('\(([\w\s,]+)\)', next_line) + if match: + return [arg.strip() for arg in match.group(1).split(',')] + else: + return None + + +def parse_comment(doc_comment, next_line): + r""" + Split the raw comment text into a dictionary of tags. The main comment + body is included as 'doc'. + + >>> comment = get_doc_comments(read_file('examples/module.js'))[4][0] + >>> parse_comment(strip_stars(comment), '')['doc'] + 'This is the documentation for the fourth function.\n\n Since the function being documented is itself generated from another\n function, its name needs to be specified explicitly. using the @function tag' + >>> parse_comment(strip_stars(comment), '')['function'] + 'not_auto_discovered' + + If there are multiple tags with the same name, they're included as a list: + + >>> parse_comment(strip_stars(comment), '')['param'] + ['{String} arg1 The first argument.', '{Int} arg2 The second argument.'] + + """ + sections = re.split('\n\s*@', doc_comment) + tags = { + 'doc': sections[0].strip(), + 'guessed_function': guess_function_name(next_line), + 'guessed_params': guess_parameters(next_line) + } + for section in sections[1:]: + tag, body = split_tag(section) + if tag in tags: + existing = tags[tag] + try: + existing.append(body) + except AttributeError: + tags[tag] = [existing, body] + else: + tags[tag] = body + return tags + + +#### Classes ##### + +class CommentDoc(object): + """ + Base class for all classes that represent a parsed comment of some sort. + """ + + def __init__(self, parsed_comment): + self.parsed = parsed_comment + + def __str__(self): + return "Docs for " + self.name + + def __repr__(self): + return str(self) + + def __contains__(self, tag_name): + return tag_name in self.parsed + + def __getitem__(self, tag_name): + return self.get(tag_name) + + def get(self, tag_name, default=''): + """ + Return the value of a particular tag, or None if that tag doesn't + exist. Use 'doc' for the comment body itself. + """ + return self.parsed.get(tag_name, default) + + def get_as_list(self, tag_name): + """ + Return the value of a tag, making sure that it's a list. Absent + tags are returned as an empty-list; single tags are returned as a + one-element list. + + The returned list is a copy, and modifications do not affect the + original object. + """ + val = self.get(tag_name, []) + if isinstance(val, list): + return val[:] + else: + return [val] + + @property + def doc(self): + """ + Return the comment body. + """ + return self.get('doc') + + @property + def url(self): + """ + Return a URL for the comment, within the page. + """ + return '#' + self.name + + @property + def see(self): + """ + Return a list of all @see tags on the comment. + """ + return self.get_as_list('see') + + def to_json(self): + """ + Return a JSON representation of the CommentDoc. Keys are as per + to_dict. + """ + return json.dumps(self.to_dict()) + + def to_dict(self): + """ + Return a dictionary representation of the CommentDoc. The keys of + this correspond to the tags in the comment, with the comment body in + `doc`. + """ + return self.parsed.copy() + +class ParamDoc(object): + """ + Represents a parameter, option, or parameter-like object, basically + anything that has a name, a type, and a description, separated by spaces. + This is also used for return types and exceptions, which use an empty + string for the name. + + >>> param = ParamDoc('{Array<DOM>} elems The elements to act upon') + >>> param.name + 'elems' + >>> param.doc + 'The elements to act upon' + >>> param.type + 'Array<DOM>' + + You can also omit the type: if the first element is not surrounded by + curly braces, it's assumed to be the name instead: + + >>> param2 = ParamDoc('param1 The first param') + >>> param2.type + '' + >>> param2.name + 'param1' + >>> param2.doc + 'The first param' + + """ + + def __init__(self, text): + parsed = list(split_delimited('{}', ' ', text)) + if parsed[0].startswith('{') and parsed[0].endswith('}'): + self.type = parsed[0][1:-1] + self.name = parsed[1] + self.doc = ' '.join(parsed[2:]) + else: + self.type = '' + self.name = parsed[0] + self.doc = ' '.join(parsed[1:]) + + def to_dict(self): + """ + Convert this to a dict. Keys (all strings) are: + + - **name**: Parameter name + - **type**: Parameter type + - **doc**: Parameter description + """ + return { + 'name': self.name, + 'type': self.type, + 'doc': self.doc + } + + def to_html(self, css_class=''): + """ + Returns the parameter as a dt/dd pair. + """ + if self.name and self.type: + header_text = '%s (%s)' % (self.name, self.type) + elif self.type: + header_text = self.type + else: + header_text = self.name + return '<dt>%s</dt><dd>%s</dd>' % (header_text, self.doc) + + +##### DEPENDENCIES ##### + +class CyclicDependency(Exception): + """ + Exception raised if there is a cyclic dependency. + """ + + def __init__(self, remaining_dependencies): + self.values = remaining_dependencies + + def __str__(self): + return ('The following dependencies result in a cycle: ' + + ', '.join(self.values)) + + +class MissingDependency(Exception): + """ + Exception raised if a file references a dependency that doesn't exist. + """ + + def __init__(self, file, dependency): + self.file = file + self.dependency = dependency + + def __str__(self): + return "Couldn't find dependency %s when processing %s" % \ + (self.dependency, self.file) + + +def build_dependency_graph(start_nodes, js_doc): + """ + Build a graph where nodes are filenames and edges are reverse dependencies + (so an edge from jquery.js to jquery.dimensions.js indicates that jquery.js + must be included before jquery.dimensions.js). The graph is represented + as a dictionary from filename to (in-degree, edges) pair, for ease of + topological sorting. Also returns a list of nodes of degree zero. + """ + queue = [] + dependencies = {} + start_sort = [] + + def add_vertex(file): + in_degree = len(js_doc[file].module.dependencies) + dependencies[file] = [in_degree, []] + queue.append(file) + if in_degree == 0: + start_sort.append(file) + + def add_edge(from_file, to_file): + dependencies[from_file][1].append(to_file) + + def is_in_graph(file): + return file in dependencies + + for file in start_nodes: + add_vertex(file) + for file in queue: + for dependency in js_doc[file].module.dependencies: + if dependency not in js_doc: + raise MissingDependency(file, dependency) + if not is_in_graph(dependency): + add_vertex(dependency) + add_edge(dependency, file) + return dependencies, start_sort + + +def topological_sort(dependencies, start_nodes): + """ + Perform a topological sort on the dependency graph `dependencies`, starting + from list `start_nodes`. + """ + retval = [] + + def edges(node): + return dependencies[node][1] + + def in_degree(node): + return dependencies[node][0] + + def remove_incoming(node): + dependencies[node][0] = in_degree(node) - 1 + + while start_nodes: + node = start_nodes.pop() + retval.append(node) + for child in edges(node): + remove_incoming(child) + if not in_degree(child): + start_nodes.append(child) + leftover_nodes = [node for node in dependencies.keys() + if in_degree(node) > 0] + if leftover_nodes: + raise CyclicDependency(leftover_nodes) + else: + return retval + + +def find_dependencies(start_nodes, js_doc): + """ + Sort the dependency graph, taking in a list of starting module names and a + CodeBaseDoc (or equivalent dictionary). Returns an ordered list of + transitive dependencies such that no module appears before its + dependencies. + """ + return topological_sort(*build_dependency_graph(start_nodes, js_doc)) diff --git a/doc/_extensions/pyjsparser/parser.py b/doc/_extensions/pyjsparser/parser.py index 78ebe14e6ecb7cfdae0af9a5ea9ac54842617fb8..28001886a8ed3078853bb03a07c8abffd750f312 100644 --- a/doc/_extensions/pyjsparser/parser.py +++ b/doc/_extensions/pyjsparser/parser.py @@ -174,7 +174,7 @@ class PyJsParser: break else: break - return filter(None, comments) + return [c for c in comments if c] def scanHexEscape(self, prefix): code = 0 diff --git a/doc/_extensions/pyjsparser/pyjsparserdata.py b/doc/_extensions/pyjsparser/pyjsparserdata.py index 1d6ab2ee2ca802d71f5ec2c6b25bce4bb602c9ab..6ca6dbc47ccf4759f891dfb9cfd6f218461a0917 100644 --- a/doc/_extensions/pyjsparser/pyjsparserdata.py +++ b/doc/_extensions/pyjsparser/pyjsparserdata.py @@ -300,4 +300,4 @@ if __name__=='__main__': assert isLineTerminator(0x0A) assert isIdentifierStart('$') assert isIdentifierStart(100) - assert isWhiteSpace(' ') \ No newline at end of file + assert isWhiteSpace(' ') diff --git a/doc/requirements.txt b/doc/requirements.txt index 4f8b5fa4bbf2ea88944dadd9592eff1383c99fea..160e375ba5563a853dc7f2e47c04c689dc2abc6b 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,2 @@ sphinx>=1.2 sphinx-patchqueue>=1.0 -PyJSDoc==0.9.1