Source code for crc_diagram.core.parsers.python_parser
# coding: utf-8
from __future__ import (
absolute_import,
unicode_literals
)
import ast
from os import path
from contextlib import closing
from crc_diagram.exceptions import ParserException
from crc_diagram.core import CRC
from crc_diagram import utils
from crc_diagram.core.parsers.base_parser import BaseParser
[docs]class PythonParser(BaseParser, ast.NodeVisitor):
"""
A parser class which extracts CRC data from docstrings using :py:data:`ast` module.
:param path_or_stream: A file path or a file like object from where the CRCs will be extracted.
:param regular_expression collaborator_pattern: A regex pattern do extract the collaborators
from docstrings.
The default is :py:data:`COLLABORATOR_PATTERN`.
:param regular_expression responsibility_pattern: A regex pattern do extract the
responsibilities from docstrings.
The default is :py:data:`RESPONSIBILITY_PATTERN`.
:param tuple allowed_file_extensions: A list of file extensions do accept.
The parser will ignore any files which extension isn`t in this list.
Example::
parser = PythonParser('file.py').parse()
parser.result
The result is a list of :py:data:`CRC` instances::
[
CRC(name='HtmlToMarkdown',
kind='class',
collaborators=['ImageUploader'],
responsibilities=['Convert html files to markdown']
),
CRC(name='ImageUploader',
kind='class',
collaborators=[],
responsibilities=['Store images in the cloud']
)
]
You can change the patterns used to extract responsibilities and collaborators
passing a regular expression to PythonParser::
responsibility_pattern = r'\s*?:responsibility:(.*)$'
parser = PythonParser(
'file.py',
responsibility_pattern=responsibility_pattern
)
This will extract any data in docstring which has the following format::
:responsibility: Save the world
The same is valid to collaborators::
collaborator_pattern = r'\s*?:collaborator:(.*)$'
parser = PythonParser(
'file.py',
collaborator_pattern=collaborator_pattern
)
And you also can pass :py:data:`re.compile` objects::
import re
collaborator_pattern = re.compile(r'\s*?:collaborator:(.*)$')
parser = PythonParser(
'file.py',
collaborator_pattern=collaborator_pattern
)
"""
def __init__(self, *args, **kwargs):
super(PythonParser, self).__init__(*args, **kwargs)
if not self.allowed_file_extensions:
self.allowed_file_extensions = ('.py', )
[docs] def parse(self):
"""
The main functionality. It parses a tree from :py:data:`self.path_or_stream`
and uses :py:data:`ast.NodeVisitor` to iterate over the tree.
:returns: self
"""
self.stream.seek(0)
try:
with closing(self.stream) as stream:
tree = ast.parse(stream.read())
except (SyntaxError,):
raise ParserException('File {file_} is not a python file'.format(
file_=self.stream.name
))
else:
self.visit(tree)
return self
def _add_crc(self, node, kind, name):
self.current_crc = CRC(name, kind)
docstring = ast.get_docstring(node) or ""
for line in docstring.split('\n'):
self.get_collaborator(line)
self.get_responsibility(line)
self._crcs.append(self.current_crc)
def visit_Module(self, node):
filename = path.split(self.stream.name)[-1]
name, _ = utils.split_by_extension(filename)
self._add_crc(node, 'module', name)
return self.generic_visit(node)
def visit_ClassDef(self, node):
self._add_crc(node, 'class', node.name)