~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to docs/source/sphinxext/comment_eater.py

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2014-06-01 17:04:08 UTC
  • mfrom: (7.2.5 sid)
  • Revision ID: package-import@ubuntu.com-20140601170408-m86xvdjd83a4qon0
Tags: 4.4.1-1ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
 - Let the binary-predeb target work on the usr/lib/python* directory
   as we don't have usr/share/pyshared anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from cStringIO import StringIO
2
 
import compiler
3
 
import inspect
4
 
import textwrap
5
 
import tokenize
6
 
 
7
 
from compiler_unparse import unparse
8
 
 
9
 
 
10
 
class Comment(object):
11
 
    """ A comment block.
12
 
    """
13
 
    is_comment = True
14
 
    def __init__(self, start_lineno, end_lineno, text):
15
 
        # int : The first line number in the block. 1-indexed.
16
 
        self.start_lineno = start_lineno
17
 
        # int : The last line number. Inclusive!
18
 
        self.end_lineno = end_lineno
19
 
        # str : The text block including '#' character but not any leading spaces.
20
 
        self.text = text
21
 
 
22
 
    def add(self, string, start, end, line):
23
 
        """ Add a new comment line.
24
 
        """
25
 
        self.start_lineno = min(self.start_lineno, start[0])
26
 
        self.end_lineno = max(self.end_lineno, end[0])
27
 
        self.text += string
28
 
 
29
 
    def __repr__(self):
30
 
        return '%s(%r, %r, %r)' % (self.__class__.__name__, self.start_lineno,
31
 
            self.end_lineno, self.text)
32
 
 
33
 
 
34
 
class NonComment(object):
35
 
    """ A non-comment block of code.
36
 
    """
37
 
    is_comment = False
38
 
    def __init__(self, start_lineno, end_lineno):
39
 
        self.start_lineno = start_lineno
40
 
        self.end_lineno = end_lineno
41
 
 
42
 
    def add(self, string, start, end, line):
43
 
        """ Add lines to the block.
44
 
        """
45
 
        if string.strip():
46
 
            # Only add if not entirely whitespace.
47
 
            self.start_lineno = min(self.start_lineno, start[0])
48
 
            self.end_lineno = max(self.end_lineno, end[0])
49
 
 
50
 
    def __repr__(self):
51
 
        return '%s(%r, %r)' % (self.__class__.__name__, self.start_lineno,
52
 
            self.end_lineno)
53
 
 
54
 
 
55
 
class CommentBlocker(object):
56
 
    """ Pull out contiguous comment blocks.
57
 
    """
58
 
    def __init__(self):
59
 
        # Start with a dummy.
60
 
        self.current_block = NonComment(0, 0)
61
 
 
62
 
        # All of the blocks seen so far.
63
 
        self.blocks = []
64
 
 
65
 
        # The index mapping lines of code to their associated comment blocks.
66
 
        self.index = {}
67
 
 
68
 
    def process_file(self, file):
69
 
        """ Process a file object.
70
 
        """
71
 
        for token in tokenize.generate_tokens(file.next):
72
 
            self.process_token(*token)
73
 
        self.make_index()
74
 
 
75
 
    def process_token(self, kind, string, start, end, line):
76
 
        """ Process a single token.
77
 
        """
78
 
        if self.current_block.is_comment:
79
 
            if kind == tokenize.COMMENT:
80
 
                self.current_block.add(string, start, end, line)
81
 
            else:
82
 
                self.new_noncomment(start[0], end[0])
83
 
        else:
84
 
            if kind == tokenize.COMMENT:
85
 
                self.new_comment(string, start, end, line)
86
 
            else:
87
 
                self.current_block.add(string, start, end, line)
88
 
 
89
 
    def new_noncomment(self, start_lineno, end_lineno):
90
 
        """ We are transitioning from a noncomment to a comment.
91
 
        """
92
 
        block = NonComment(start_lineno, end_lineno)
93
 
        self.blocks.append(block)
94
 
        self.current_block = block
95
 
 
96
 
    def new_comment(self, string, start, end, line):
97
 
        """ Possibly add a new comment.
98
 
 
99
 
        Only adds a new comment if this comment is the only thing on the line.
100
 
        Otherwise, it extends the noncomment block.
101
 
        """
102
 
        prefix = line[:start[1]]
103
 
        if prefix.strip():
104
 
            # Oops! Trailing comment, not a comment block.
105
 
            self.current_block.add(string, start, end, line)
106
 
        else:
107
 
            # A comment block.
108
 
            block = Comment(start[0], end[0], string)
109
 
            self.blocks.append(block)
110
 
            self.current_block = block
111
 
 
112
 
    def make_index(self):
113
 
        """ Make the index mapping lines of actual code to their associated
114
 
        prefix comments.
115
 
        """
116
 
        for prev, block in zip(self.blocks[:-1], self.blocks[1:]):
117
 
            if not block.is_comment:
118
 
                self.index[block.start_lineno] = prev
119
 
 
120
 
    def search_for_comment(self, lineno, default=None):
121
 
        """ Find the comment block just before the given line number.
122
 
 
123
 
        Returns None (or the specified default) if there is no such block.
124
 
        """
125
 
        if not self.index:
126
 
            self.make_index()
127
 
        block = self.index.get(lineno, None)
128
 
        text = getattr(block, 'text', default)
129
 
        return text
130
 
 
131
 
 
132
 
def strip_comment_marker(text):
133
 
    """ Strip # markers at the front of a block of comment text.
134
 
    """
135
 
    lines = []
136
 
    for line in text.splitlines():
137
 
        lines.append(line.lstrip('#'))
138
 
    text = textwrap.dedent('\n'.join(lines))
139
 
    return text
140
 
 
141
 
 
142
 
def get_class_traits(klass):
143
 
    """ Yield all of the documentation for trait definitions on a class object.
144
 
    """
145
 
    # FIXME: gracefully handle errors here or in the caller?
146
 
    source = inspect.getsource(klass)
147
 
    cb = CommentBlocker()
148
 
    cb.process_file(StringIO(source))
149
 
    mod_ast = compiler.parse(source)
150
 
    class_ast = mod_ast.node.nodes[0]
151
 
    for node in class_ast.code.nodes:
152
 
        # FIXME: handle other kinds of assignments?
153
 
        if isinstance(node, compiler.ast.Assign):
154
 
            name = node.nodes[0].name
155
 
            rhs = unparse(node.expr).strip()
156
 
            doc = strip_comment_marker(cb.search_for_comment(node.lineno, default=''))
157
 
            yield name, rhs, doc
158