2
\$\begingroup\$

Generating documentation for VBA code

The goal of this exercise to is create an application which can read module files created in VBA project and generate markdown documentation for them.

Key features

  • User should be able to pass standard module (bas) based on which markdown file is created.

Limitations

  • There are more more types than just standard module (bas) in VBA project. For now, they are not supported.

  • Entry point of application (main) is yet not defined.

Project structure

vba-doc-py
│   module_parser.py
│   module_parser_tests.py
│   method_parser.py
│   method_parser_tests.py
│   module_doc.py
│   module_doc_tests.py

Code

module_parser.py

import module_doc


class ModuleParser():
    """This parser is responsible for extracting information from raw VBA code
    which later, can be used for generating markdown document."""

    def make(self, code):
        lines = code.split('\n')

        for ln in lines:
            if 'Attribute VB_Name' in ln:
                dbl_qut = ln.index('\"') + 1
                name = ln[dbl_qut: ln.rindex('\"')]
                doc = module_doc.ModuleDoc(name)

            elif 'Public Sub' in ln or 'Public Function' in ln:
                doc.addMethod(self.__get_method_name(ln), self.__get_args(ln))

        return doc.build()

    def __get_method_name(self, ln):
        method_type = 'Sub' if ' Sub ' in ln else 'Function'
        name_start = len(f'Public {method_type} ')
        open_parenthesis = ln.index('(')
        return ln[name_start:open_parenthesis]

    def __get_args(self, ln):
        open_parentheses = ln.index('(')
        close_parentheses = ln.rindex(')')

        if self.__no_args(open_parentheses, close_parentheses):
            return ''

        # Remove surrounding parentheses
        args = ln[open_parentheses + 1: close_parentheses]

        args_list = args.split(', ')
        output = []

        AS_KEYWORD = ' As '
        for item in args_list:
            as_keyword_start = item.index(AS_KEYWORD)
            arg_type = item[as_keyword_start + len(AS_KEYWORD):]
            output.append(arg_type)

        return output

    def __no_args(self, open_parentheses, close_parentheses):
        return open_parentheses + 1 == close_parentheses

    def __remove(self, text, start_index, count):
        """Returns a new string in which a specified number of characters in the current 
        instance beginning at a specified position have been deleted."""

        return text[:start_index] + text[start_index + count:]

method_parser.py

class MethodParser():
    """Class responsible for parsing VBA methods (both Sub and Function)
    and turning them into specified markdown format."""

    def make(self, code):
        return f'# {self.__get_method_name(code)}\n\n```vb\n{code}\n```'

    def __get_method_name(self, ln):
        method_type = 'Sub' if ' Sub ' in ln else 'Function'
        name_start = len(f'Public {method_type} ')
        open_parentheses = ln.index('(')
        return ln[name_start:open_parentheses]

module_doc.py

class ModuleDoc():
    """Represents markdown document on the module level."""

    def __init__(self, name):
        self.name = name
        self.subs = {}

    def addMethod(self, name, args):
        self.subs.update({name: args})

    def build(self):
        output = '# ' + self.name + ' module\n\n'
        if len(self.subs) > 0:
            output += ('# Methods\n\n'
                       '|Name|Description|\n'
                       '|-|-|\n')

            for name in self.subs:
                output += f'|[{name} ({self.__format_args(self.subs[name])})](./{name}.md)||\n'

        return output

    def __format_args(self, args):
        if isinstance(args, list):
            return ', '.join(args)
        else:
            return args

Conclusion

I'm working mainly with C#/VBA and that is mostly a style which I apply here, because it is the style which I know best. Also I'm aware that Python most definitely have it's own style which I'm looking forward to explore.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.