2 # -*- coding: utf-8 -*-
4 # codimension - graphics python two-way code editor and analyzer
5 # Copyright (C) 2010 Sergey Satskiy <sergey.satskiy@gmail.com>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 """ The file holds types and a glue code between python and C python
27 from sys
import maxint
31 def trim_docstring( docstring
):
32 " Taken from http://www.python.org/dev/peps/pep-0257/ "
37 # Convert tabs to spaces (following the normal Python rules)
38 # and split into a list of lines:
39 lines
= docstring
.expandtabs().splitlines()
41 # Determine minimum indentation (first line doesn't count):
43 for line
in lines
[ 1: ]:
44 stripped
= line
.lstrip()
46 indent
= min( indent
, len( line
) - len( stripped
) )
48 # Remove indentation (first line is special):
49 lines
[ 0 ] = lines
[ 0 ].strip()
52 for line
in lines
[ 1: ]:
53 lines
[ index
] = line
[ indent
: ].rstrip()
56 # Strip off trailing and leading blank lines:
57 while lines
and not lines
[ -1 ]:
59 while lines
and not lines
[ 0 ]:
62 # Return a single string:
63 return '\n'.join( lines
)
67 class ModuleInfoBase( object ):
68 " Common part for the module information "
70 __slots__
= [ "name", "line", "pos", "absPosition" ]
72 def __init__( self
, name
, line
, pos
, absPosition
):
73 self
.name
= name
# Item identifier
74 self
.line
= line
# Line number where item is found (1-based)
75 self
.pos
= pos
# Item position in the line (1-based)
77 self
.absPosition
= absPosition
# Absolute position of the first item
78 # identifier character starting from
79 # the beginning of the input stream
83 def isPrivate( self
):
84 " True if it is private "
85 return self
.name
.startswith( '__' )
87 def isProtected( self
):
88 " True if it is protected "
89 return not self
.isPrivate() and self
.name
.startswith( '_' )
92 " Provides line, pos and absPosition line as string "
93 return str( self
.line
) + ":" + \
94 str( self
.pos
) + ":" + \
95 str( self
.absPosition
)
97 def getDisplayName( self
):
98 """ Provides a name for display purpose.
99 It is going to be overriden in deriving classes """
103 class Encoding( ModuleInfoBase
):
104 " Holds information about encoding string "
108 def __init__( self
, encName
, line
, pos
, absPosition
):
109 ModuleInfoBase
.__init
__( self
, encName
, line
, pos
, absPosition
)
113 return "Encoding[" + self
._getLPA
() + "]: '" + self
.name
+ "'"
116 class ImportWhat( ModuleInfoBase
):
117 " Holds information about a single imported item "
119 __slots__
= [ "alias" ]
121 def __init__( self
, whatName
, line
, pos
, absPosition
):
122 ModuleInfoBase
.__init
__( self
, whatName
, line
, pos
, absPosition
)
128 return self
.name
+ "[" + self
._getLPA
() + "]"
129 return self
.name
+ "[" + self
._getLPA
() + "] as " + self
.alias
131 def getDisplayName( self
):
132 " Provides a name for display purpose respecting the alias "
135 return self
.name
+ " as " + self
.alias
138 class Import( ModuleInfoBase
):
139 " Holds information about a single import name "
141 __slots__
= [ "alias", "what" ]
143 def __init__( self
, importName
, line
, pos
, absPosition
):
144 ModuleInfoBase
.__init
__( self
, importName
, line
, pos
, absPosition
)
150 out
= "Import[" + self
._getLPA
() + "]: '" + self
.name
+ "'"
152 out
+= " as '" + self
.alias
+ "'"
154 for item
in self
.what
:
155 whatPart
+= "\n " + str( item
)
156 return out
+ whatPart
158 def getDisplayName( self
):
159 " Provides a name for display purpose respecting the alias "
162 return self
.name
+ " as " + self
.alias
165 class Global( ModuleInfoBase
):
166 " Holds information about a single global variable "
170 def __init__( self
, globalName
, line
, pos
, absPosition
):
171 ModuleInfoBase
.__init
__( self
, globalName
, line
, pos
, absPosition
)
175 return "Global[" + self
._getLPA
() + "]: '" + self
.name
+ "'"
178 class ClassAttribute( ModuleInfoBase
):
179 " Holds information about a class attribute "
183 def __init__( self
, attrName
, line
, pos
, absPosition
):
184 ModuleInfoBase
.__init
__( self
, attrName
, line
, pos
, absPosition
)
188 return "Class attribute[" + self
._getLPA
() + "]: '" + self
.name
+ "'"
191 class InstanceAttribute( ModuleInfoBase
):
192 " Holds information about a class instance attribute "
196 def __init__( self
, attrName
, line
, pos
, absPosition
):
197 ModuleInfoBase
.__init
__( self
, attrName
, line
, pos
, absPosition
)
201 return "Instance attribute[" + self
._getLPA
() + "]: '" + self
.name
+ "'"
204 class Decorator( ModuleInfoBase
):
205 " Holds information about a class/function decorator "
207 __slots__
= [ "arguments" ]
209 def __init__( self
, decorName
, line
, pos
, absPosition
):
210 ModuleInfoBase
.__init
__( self
, decorName
, line
, pos
, absPosition
)
215 val
= "Decorator[" + self
._getLPA
() + "]: '" + self
.name
219 for item
in self
.arguments
:
228 def getDisplayName( self
):
229 " Provides a name for display purpose "
230 displayName
= self
.name
232 displayName
+= "( " + ", ".join( self
.arguments
) + " )"
236 class Docstring( object ):
237 " Holds a docstring information "
239 __slots__
= [ "line", "text" ]
241 def __init__( self
, text
, line
):
247 return "Docstring[" + str( self
.line
) + "]: '" + self
.text
+ "'"
250 class Function( ModuleInfoBase
):
251 " Holds information about a single function"
253 __slots__
= [ "keywordLine", "keywordPos", "colonLine", "colonPos",
254 "docstring", "arguments", "decorators", "functions",
257 def __init__( self
, funcName
, line
, pos
, absPosition
,
258 keywordLine
, keywordPos
,
259 colonLine
, colonPos
):
260 ModuleInfoBase
.__init
__( self
, funcName
, line
, pos
, absPosition
)
262 self
.keywordLine
= keywordLine
# line where 'def' keyword
264 self
.keywordPos
= keywordPos
# pos where 'def' keyword
266 self
.colonLine
= colonLine
# line where ':' char starts (1-based)
267 self
.colonPos
= colonPos
# pos where ':' char starts (1-based)
269 self
.docstring
= None
272 self
.functions
= [] # nested functions
273 self
.classes
= [] # nested classes
276 def isStaticMethod( self
):
277 " Returns True if it is a static method "
278 for item
in self
.decorators
:
279 if item
.name
== 'staticmethod':
283 def niceStringify( self
, level
):
284 " Returns a string representation with new lines and shifts "
286 out
= level
* " " + "Function[" + str(self
.keywordLine
) + \
287 ":" + str(self
.keywordPos
) + \
288 ":" + self
._getLPA
() + \
289 ":" + str(self
.colonLine
) + \
290 ":" + str(self
.colonPos
) + \
291 "]: '" + self
.name
+ "'"
292 for item
in self
.arguments
:
293 out
+= '\n' + level
* " " + "Argument: '" + item
+ "'"
294 for item
in self
.decorators
:
295 out
+= '\n' + level
* " " + str( item
)
296 if self
.docstring
is not None:
297 out
+= '\n' + level
* " " + str( self
.docstring
)
298 for item
in self
.functions
:
299 out
+= '\n' + item
.niceStringify( level
+ 1 )
300 for item
in self
.classes
:
301 out
+= '\n' + item
.niceStringify( level
+ 1 )
304 def getDisplayName( self
):
305 " Provides a name for display purpose "
306 displayName
= self
.name
+ "("
308 displayName
+= " " + ", ".join( self
.arguments
) + " "
313 class Class( ModuleInfoBase
):
314 " Holds information about a single class"
316 __slots__
= [ "keywordLine", "keywordPos", "colonLine", "colonPos",
317 "docstring", "base", "decorators", "classAttributes",
318 "instanceAttributes", "functions", "classes" ]
320 def __init__( self
, className
, line
, pos
, absPosition
,
321 keywordLine
, keywordPos
,
322 colonLine
, colonPos
):
323 ModuleInfoBase
.__init
__( self
, className
, line
, pos
, absPosition
)
325 self
.keywordLine
= keywordLine
# line where 'def' keyword
327 self
.keywordPos
= keywordPos
# pos where 'def' keyword
329 self
.colonLine
= colonLine
# line where ':' char starts (1-based)
330 self
.colonPos
= colonPos
# pos where ':' char starts (1-based)
332 self
.docstring
= None
335 self
.classAttributes
= []
336 self
.instanceAttributes
= []
337 self
.functions
= [] # member functions
338 self
.classes
= [] # nested classes
341 def niceStringify( self
, level
):
342 " Returns a string representation with new lines and shifts "
344 out
= level
* " " + "Class[" + str(self
.keywordLine
) + \
345 ":" + str(self
.keywordPos
) + \
346 ":" + self
._getLPA
() + \
347 ":" + str(self
.colonLine
) + \
348 ":" + str(self
.colonPos
) + \
349 "]: '" + self
.name
+ "'"
350 for item
in self
.base
:
351 out
+= '\n' + level
* " " + "Base class: '" + item
+ "'"
352 for item
in self
.decorators
:
353 out
+= '\n' + level
* " " + str( item
)
354 if self
.docstring
is not None:
355 out
+= '\n' + level
* " " + str( self
.docstring
)
356 for item
in self
.classAttributes
:
357 out
+= '\n' + level
* " " + str(item
)
358 for item
in self
.instanceAttributes
:
359 out
+= '\n' + level
* " " + str(item
)
360 for item
in self
.functions
:
361 out
+= '\n' + item
.niceStringify( level
+ 1 )
362 for item
in self
.classes
:
363 out
+= '\n' + item
.niceStringify( level
+ 1 )
366 def getDisplayName( self
):
367 " Provides a name for display purpose "
368 displayName
= self
.name
370 displayName
+= "( " + ", ".join( self
.base
) + " )"
374 class BriefModuleInfo( object ):
375 " Holds a single module content information "
377 __slots__
= [ "isOK", "docstring", "encoding", "imports", "globals",
378 "functions", "classes", "errors", "lexerErrors",
379 "objectsStack", "__lastImport", "__lastDecorators" ]
381 def __init__( self
):
384 self
.docstring
= None
391 self
.lexerErrors
= []
393 self
.objectsStack
= []
394 self
.__lastImport
= None
395 self
.__lastDecorators
= None
398 def niceStringify( self
):
399 " Returns a string representation with new lines and shifts "
402 if self
.docstring
is not None:
403 out
+= str( self
.docstring
)
404 if not self
.encoding
is None:
407 out
+= str( self
.encoding
)
408 for item
in self
.imports
:
412 for item
in self
.globals:
416 for item
in self
.functions
:
419 out
+= item
.niceStringify( 0 )
420 for item
in self
.classes
:
423 out
+= item
.niceStringify( 0 )
428 " Flushes the collected information "
429 self
.__flushLevel
( 0 )
430 if self
.__lastImport
is not None:
431 self
.imports
.append( self
.__lastImport
)
434 def __flushLevel( self
, level
):
435 " Merge the found objects to the required level "
437 objectsCount
= len( self
.objectsStack
)
439 while objectsCount
> level
:
440 lastIndex
= objectsCount
- 1
442 # We have exactly one element in the stack
443 if self
.objectsStack
[ 0 ].__class
__.__name
__ == "Class":
444 self
.classes
.append( self
.objectsStack
[ 0 ] )
446 self
.functions
.append( self
.objectsStack
[ 0 ] )
447 self
.objectsStack
= []
450 # Append to the previous level
451 if self
.objectsStack
[ lastIndex
].__class
__.__name
__ == "Class":
452 self
.objectsStack
[ lastIndex
- 1 ].classes
. \
453 append( self
.objectsStack
[ lastIndex
] )
455 self
.objectsStack
[ lastIndex
- 1 ].functions
. \
456 append( self
.objectsStack
[ lastIndex
] )
458 del self
.objectsStack
[ lastIndex
]
463 def _onEncoding( self
, encString
, line
, pos
, absPosition
):
464 " Memorizes module encoding "
465 self
.encoding
= Encoding( encString
, line
, pos
, absPosition
)
468 def _onGlobal( self
, name
, line
, pos
, absPosition
, level
):
469 " Memorizes a global variable "
472 for item
in self
.globals:
473 if item
.name
== name
:
475 self
.globals.append( Global( name
, line
, pos
, absPosition
) )
478 def _onClass( self
, name
, line
, pos
, absPosition
,
479 keywordLine
, keywordPos
,
480 colonLine
, colonPos
, level
):
481 " Memorizes a class "
482 self
.__flushLevel
( level
)
483 c
= Class( name
, line
, pos
, absPosition
, keywordLine
, keywordPos
,
484 colonLine
, colonPos
)
485 if self
.__lastDecorators
is not None:
486 c
.decorators
= self
.__lastDecorators
487 self
.__lastDecorators
= None
488 self
.objectsStack
.append( c
)
491 def _onFunction( self
, name
, line
, pos
, absPosition
,
492 keywordLine
, keywordPos
,
493 colonLine
, colonPos
, level
):
494 " Memorizes a function "
495 self
.__flushLevel
( level
)
496 f
= Function( name
, line
, pos
, absPosition
, keywordLine
, keywordPos
,
497 colonLine
, colonPos
)
498 if self
.__lastDecorators
is not None:
499 f
.decorators
= self
.__lastDecorators
500 self
.__lastDecorators
= None
501 self
.objectsStack
.append( f
)
504 def _onImport( self
, name
, line
, pos
, absPosition
):
505 " Memorizes an import "
506 if self
.__lastImport
is not None:
507 self
.imports
.append( self
.__lastImport
)
508 self
.__lastImport
= Import( name
, line
, pos
, absPosition
)
511 def _onAs( self
, name
):
512 " Memorizes an alias for an import or an imported item "
513 if self
.__lastImport
.what
:
514 self
.__lastImport
.what
[ -1 ].alias
= name
516 self
.__lastImport
.alias
= name
519 def _onWhat( self
, name
, line
, pos
, absPosition
):
520 " Memorizes an imported item "
521 self
.__lastImport
.what
.append( ImportWhat( name
, line
, pos
,
525 def _onClassAttribute( self
, name
, line
, pos
, absPosition
, level
):
526 " Memorizes a class attribute "
527 # A class must be on the top of the stack
528 attributes
= self
.objectsStack
[ level
].classAttributes
529 for item
in attributes
:
530 if item
.name
== name
:
532 attributes
.append( ClassAttribute( name
, line
, pos
, absPosition
) )
535 def _onInstanceAttribute( self
, name
, line
, pos
, absPosition
, level
):
536 " Memorizes a class instance attribute "
537 # Instance attributes may appear in member functions only so we already
538 # have a function on the stack of objects. To get the class object one
539 # more step is required so we -1 here.
540 attributes
= self
.objectsStack
[ level
- 1 ].instanceAttributes
541 for item
in attributes
:
542 if item
.name
== name
:
544 attributes
.append( InstanceAttribute( name
, line
, pos
, absPosition
) )
547 def _onDecorator( self
, name
, line
, pos
, absPosition
):
548 " Memorizes a function or a class decorator "
549 # A class or a function must be on the top of the stack
550 d
= Decorator( name
, line
, pos
, absPosition
)
551 if self
.__lastDecorators
is None:
552 self
.__lastDecorators
= [ d
]
554 self
.__lastDecorators
.append( d
)
557 def _onDecoratorArgument( self
, name
):
558 " Memorizes a decorator argument "
559 self
.__lastDecorators
[ -1 ].arguments
.append( name
)
562 def _onDocstring( self
, docstr
, line
):
563 " Memorizes a function/class/module docstring "
564 if self
.objectsStack
:
565 self
.objectsStack
[ -1 ].docstring
= \
566 Docstring( trim_docstring( docstr
), line
)
569 self
.docstring
= Docstring( trim_docstring( docstr
), line
)
572 def _onArgument( self
, name
):
573 " Memorizes a function argument "
574 self
.objectsStack
[ -1 ].arguments
.append( name
)
577 def _onArgumentValue( self
, value
):
578 " Memorizes a function argument value "
579 self
.objectsStack
[ -1 ].arguments
[ -1 ] += " = " + value
582 def _onBaseClass( self
, name
):
583 " Memorizes a class base class "
584 # A class must be on the top of the stack
585 self
.objectsStack
[ -1 ].base
.append( name
)
588 def _onError( self
, message
):
589 " Memorizies a parser error message "
591 if message
.strip() != "":
592 self
.errors
.append( message
)
595 def _onLexerError( self
, message
):
596 " Memorizes a lexer error message "
598 if message
.strip() != "":
599 self
.lexerErrors
.append( message
)
603 def getBriefModuleInfoFromFile( fileName
):
604 " Builds the brief module info from file "
606 modInfo
= BriefModuleInfo()
607 _cdmpyparser
.getBriefModuleInfoFromFile( modInfo
, fileName
)
612 def getBriefModuleInfoFromMemory( content
):
613 " Builds the brief module info from memory "
615 modInfo
= BriefModuleInfo()
616 _cdmpyparser
.getBriefModuleInfoFromMemory( modInfo
, content
)
621 " Provides the parser version "
622 return _cdmpyparser
.version