1 # |
|
2 # Jasy - Web Tooling Framework |
|
3 # Copyright 2010-2012 Zynga Inc. |
|
4 # |
|
5 |
|
6 from __future__ import unicode_literals |
|
7 |
|
8 import re |
|
9 |
|
10 import jasy.core.Console as Console |
|
11 |
|
12 __all__ = ["CommentException", "Comment"] |
|
13 |
|
14 |
|
15 # Used to measure the doc indent size (with leading stars in front of content) |
|
16 docIndentReg = re.compile(r"^(\s*\*\s*)(\S*)") |
|
17 |
|
18 |
|
19 class CommentException(Exception): |
|
20 """ |
|
21 Thrown when errors during comment processing are detected. |
|
22 """ |
|
23 def __init__(self, message, lineNo=0): |
|
24 Exception.__init__(self, "Comment error: %s (line: %s)" % (message, lineNo+1)) |
|
25 |
|
26 |
|
27 class Comment(): |
|
28 """ |
|
29 Comment class is attached to parsed nodes and used to store all comment related |
|
30 information. |
|
31 |
|
32 The class supports a new Markdown and TomDoc inspired dialect to make developers life |
|
33 easier and work less repeative. |
|
34 """ |
|
35 |
|
36 # Relation to code |
|
37 context = None |
|
38 |
|
39 # Collected text of the comment |
|
40 text = None |
|
41 |
|
42 def __init__(self, text, context=None, lineNo=0, indent="", fileId=None): |
|
43 |
|
44 # Store context (relation to code) |
|
45 self.context = context |
|
46 |
|
47 # Store fileId |
|
48 self.fileId = fileId |
|
49 |
|
50 # Figure out the type of the comment based on the starting characters |
|
51 |
|
52 # Inline comments |
|
53 if text.startswith("//"): |
|
54 # "// hello" => " hello" |
|
55 text = " " + text[2:] |
|
56 self.variant = "single" |
|
57 |
|
58 # Doc comments |
|
59 elif text.startswith("/**"): |
|
60 # "/** hello */" => " hello " |
|
61 text = " " + text[3:-2] |
|
62 self.variant = "doc" |
|
63 |
|
64 # Protected comments which should not be removed |
|
65 # (e.g these are used for license blocks) |
|
66 elif text.startswith("/*!"): |
|
67 # "/*! hello */" => " hello " |
|
68 text = " " + text[3:-2] |
|
69 self.variant = "protected" |
|
70 |
|
71 # A normal multiline comment |
|
72 elif text.startswith("/*"): |
|
73 # "/* hello */" => " hello " |
|
74 text = " " + text[2:-2] |
|
75 self.variant = "multi" |
|
76 |
|
77 else: |
|
78 raise CommentException("Invalid comment text: %s" % text, lineNo) |
|
79 |
|
80 # Multi line comments need to have their indentation removed |
|
81 if "\n" in text: |
|
82 text = self.__outdent(text, indent, lineNo) |
|
83 |
|
84 # For single line comments strip the surrounding whitespace |
|
85 else: |
|
86 # " hello " => "hello" |
|
87 text = text.strip() |
|
88 |
|
89 # The text of the comment |
|
90 self.text = text |
|
91 |
|
92 def __outdent(self, text, indent, startLineNo): |
|
93 """ |
|
94 Outdent multi line comment text and filtering empty lines |
|
95 """ |
|
96 |
|
97 lines = [] |
|
98 |
|
99 # First, split up the comments lines and remove the leading indentation |
|
100 for lineNo, line in enumerate((indent+text).split("\n")): |
|
101 |
|
102 if line.startswith(indent): |
|
103 lines.append(line[len(indent):].rstrip()) |
|
104 |
|
105 elif line.strip() == "": |
|
106 lines.append("") |
|
107 |
|
108 else: |
|
109 # Only warn for doc comments, otherwise it might just be code commented |
|
110 # out which is sometimes formatted pretty crazy when commented out |
|
111 if self.variant == "doc": |
|
112 Console.warn("Could not outdent doc comment at line %s in %s", |
|
113 startLineNo+lineNo, self.fileId) |
|
114 |
|
115 return text |
|
116 |
|
117 # Find first line with real content, then grab the one after it to get the |
|
118 # characters which need |
|
119 outdentString = "" |
|
120 for lineNo, line in enumerate(lines): |
|
121 |
|
122 if line != "" and line.strip() != "": |
|
123 matchedDocIndent = docIndentReg.match(line) |
|
124 |
|
125 if not matchedDocIndent: |
|
126 # As soon as we find a non doc indent like line we stop |
|
127 break |
|
128 |
|
129 elif matchedDocIndent.group(2) != "": |
|
130 # otherwise we look for content behind the indent to get the |
|
131 # correct real indent (with spaces) |
|
132 outdentString = matchedDocIndent.group(1) |
|
133 break |
|
134 |
|
135 lineNo += 1 |
|
136 |
|
137 # Process outdenting to all lines (remove the outdentString from the start |
|
138 # of the lines) |
|
139 if outdentString != "": |
|
140 |
|
141 lineNo = 0 |
|
142 outdentStringLen = len(outdentString) |
|
143 |
|
144 for lineNo, line in enumerate(lines): |
|
145 if len(line) <= outdentStringLen: |
|
146 lines[lineNo] = "" |
|
147 |
|
148 else: |
|
149 if not line.startswith(outdentString): |
|
150 |
|
151 # Only warn for doc comments, otherwise it might just be code |
|
152 # commented out which is sometimes formatted pretty crazy when |
|
153 # commented out |
|
154 if self.variant == "doc": |
|
155 Console.warn( |
|
156 "Invalid indentation in doc comment at line %s in %s", |
|
157 startLineNo+lineNo, self.fileId) |
|
158 |
|
159 else: |
|
160 lines[lineNo] = line[outdentStringLen:] |
|
161 |
|
162 # Merge final lines and remove leading and trailing new lines |
|
163 return "\n".join(lines).strip("\n") |
|