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.