Commit 8d09640b authored by Damien George's avatar Damien George
Browse files

stmhal: Add documentation in comments, and script to generate HTML.

Decided to write own script to pull documentation from comments in C code.
Style for writing auto generated documentation is: start line with ///
and then use standard markdown to write the comment.  Keywords
recognised by the scraper begin with backslash.  See code for examples.

Running: python gendoc.py modpyb.c accel.c adc.c dac.c extint.c i2c.c
led.c pin.c rng.c servo.c spi.c uart.c usrsw.c, will generate a HTML
structure in gendoc-out/.

gendoc.py is crude but functional.  Needed something quick, and this was
it.
parent 186e463a
......@@ -14,6 +14,13 @@
#if MICROPY_HW_HAS_MMA7660
/// \moduleref pyb
/// \class Accel - accelerometer control
///
/// Accel is an object that controls the accelerometer.
///
/// Raw values are between -30 and 30.
#define MMA_ADDR (0x98)
#define MMA_REG_X (0)
#define MMA_REG_Y (1)
......@@ -83,6 +90,8 @@ typedef struct _pyb_accel_obj_t {
STATIC pyb_accel_obj_t pyb_accel_obj;
/// \classmethod \constructor()
/// Create and return an accelerometer object.
STATIC mp_obj_t pyb_accel_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
// check arguments
mp_arg_check_num(n_args, n_kw, 0, 0, false);
......@@ -100,32 +109,38 @@ STATIC mp_obj_t read_axis(int axis) {
return mp_obj_new_int(MMA_AXIS_SIGNED_VALUE(data[0]));
}
/// \method x()
/// Get the x-axis value.
STATIC mp_obj_t pyb_accel_x(mp_obj_t self_in) {
return read_axis(MMA_REG_X);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_accel_x_obj, pyb_accel_x);
/// \method y()
/// Get the y-axis value.
STATIC mp_obj_t pyb_accel_y(mp_obj_t self_in) {
return read_axis(MMA_REG_Y);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_accel_y_obj, pyb_accel_y);
/// \method z()
/// Get the z-axis value.
STATIC mp_obj_t pyb_accel_z(mp_obj_t self_in) {
return read_axis(MMA_REG_Z);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_accel_z_obj, pyb_accel_z);
/// \method tilt()
/// Get the tilt register.
STATIC mp_obj_t pyb_accel_tilt(mp_obj_t self_in) {
uint8_t data[1];
HAL_I2C_Mem_Read(&I2CHandle1, MMA_ADDR, MMA_REG_TILT, I2C_MEMADD_SIZE_8BIT, data, 1, 200);
return mp_obj_new_int(data[0]);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_accel_tilt_obj, pyb_accel_tilt);
/// \method filtered_xyz()
/// Get a 3-tuple of filtered x, y and z values.
STATIC mp_obj_t pyb_accel_filtered_xyz(mp_obj_t self_in) {
pyb_accel_obj_t *self = self_in;
......@@ -146,7 +161,6 @@ STATIC mp_obj_t pyb_accel_filtered_xyz(mp_obj_t self_in) {
return mp_obj_new_tuple(3, tuple);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_accel_filtered_xyz_obj, pyb_accel_filtered_xyz);
STATIC mp_obj_t pyb_accel_read(mp_obj_t self_in, mp_obj_t reg) {
......@@ -154,7 +168,6 @@ STATIC mp_obj_t pyb_accel_read(mp_obj_t self_in, mp_obj_t reg) {
HAL_I2C_Mem_Read(&I2CHandle1, MMA_ADDR, mp_obj_get_int(reg), I2C_MEMADD_SIZE_8BIT, data, 1, 200);
return mp_obj_new_int(data[0]);
}
MP_DEFINE_CONST_FUN_OBJ_2(pyb_accel_read_obj, pyb_accel_read);
STATIC mp_obj_t pyb_accel_write(mp_obj_t self_in, mp_obj_t reg, mp_obj_t val) {
......@@ -163,7 +176,6 @@ STATIC mp_obj_t pyb_accel_write(mp_obj_t self_in, mp_obj_t reg, mp_obj_t val) {
HAL_I2C_Mem_Write(&I2CHandle1, MMA_ADDR, mp_obj_get_int(reg), I2C_MEMADD_SIZE_8BIT, data, 1, 200);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(pyb_accel_write_obj, pyb_accel_write);
STATIC const mp_map_elem_t pyb_accel_locals_dict_table[] = {
......
......@@ -14,16 +14,19 @@
#include "genhdr/pins.h"
#include "timer.h"
// Usage Model:
//
// adc = pyb.ADC(pin)
// val = adc.read()
//
// adc = pyb.ADCAll(resolution)
// val = adc.read_channel(channel)
// val = adc.read_core_temp()
// val = adc.read_core_vbat()
// val = adc.read_core_vref()
/// \moduleref pyb
/// \class ADC - analog to digital conversion: read analog values on a pin
///
/// Usage:
///
/// adc = pyb.ADC(pin) # create an analog object from a pin
/// val = adc.read() # read an analog value
///
/// adc = pyb.ADCAll(resolution) # creale an ADCAll object
/// val = adc.read_channel(channel) # read the given channel
/// val = adc.read_core_temp() # read MCU temperature
/// val = adc.read_core_vbat() # read MCU VBAT
/// val = adc.read_core_vref() # read MCU VREF
/* ADC defintions */
#define ADCx (ADC1)
......@@ -118,6 +121,9 @@ STATIC void adc_print(void (*print)(void *env, const char *fmt, ...), void *env,
print(env, " channel=%lu>", self->channel);
}
/// \classmethod \constructor(pin)
/// Create an ADC object associated with the given pin.
/// This allows you to then read analog values on that pin.
STATIC mp_obj_t adc_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
// check number of arguments
mp_arg_check_num(n_args, n_kw, 1, 1, false);
......@@ -155,15 +161,30 @@ STATIC mp_obj_t adc_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_
return o;
}
/// \method read()
/// Read the value on the analog pin and return it. The returned value
/// will be between 0 and 4095.
STATIC mp_obj_t adc_read(mp_obj_t self_in) {
pyb_obj_adc_t *self = self_in;
uint32_t data = adc_read_channel(&self->handle);
return mp_obj_new_int(data);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(adc_read_obj, adc_read);
/// \method read_timed(buf, freq)
/// Read analog values into the given buffer at the given frequency.
///
/// Example:
///
/// adc = pyb.ADC(pyb.Pin.board.X19) # create an ADC on pin X19
/// buf = bytearray(100) # create a buffer of 100 bytes
/// adc.read_timed(buf, 10) # read analog values into buf at 10Hz
/// # this will take 10 seconds to finish
/// for val in buf: # loop over all values
/// print(val) # print the value out
///
/// This function does not allocate any memory.
STATIC mp_obj_t adc_read_timed(mp_obj_t self_in, mp_obj_t buf_in, mp_obj_t freq_in) {
pyb_obj_adc_t *self = self_in;
......@@ -196,7 +217,6 @@ STATIC mp_obj_t adc_read_timed(mp_obj_t self_in, mp_obj_t buf_in, mp_obj_t freq_
return mp_obj_new_int(bufinfo.len);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(adc_read_timed_obj, adc_read_timed);
STATIC const mp_map_elem_t adc_locals_dict_table[] = {
......
......@@ -13,6 +13,10 @@
#include "timer.h"
#include "dac.h"
/// \moduleref pyb
/// \class DAC - digital to analog conversion
///
STATIC DAC_HandleTypeDef DAC_Handle;
void dac_init(void) {
......
......@@ -15,58 +15,52 @@
#include "pin.h"
#include "extint.h"
// Usage Model:
//
// There are a total of 22 interrupt lines. 16 of these can come from GPIO pins
// and the remaining 6 are from internal sources.
//
// For lines 0 thru 15, a given line can map to the corresponding line from an
// arbitrary port. So line 0 can map to Px0 where x is A, B, C, ... and
// line 1 can map to Px1 where x is A, B, C, ...
//
// def callback(line):
// print("line =", line)
//
// # Note: ExtInt will automatically configure the gpio line as an input.
// extint = pyb.ExtInt(pin, pyb.ExtInt.IRQ_FALLING, pyb.GPIO.PULL_UP, callback)
//
// Now every time a falling edge is seen on the X1 pin, the callback will be
// called. Caution: mechanical pushbuttons have "bounce" and pushing or
// releasing a switch will often generate multiple edges.
// See: http://www.eng.utah.edu/~cs5780/debouncing.pdf for a detailed
// explanation, along with various techniques for debouncing.
//
// Trying to register 2 callbacks onto the same pin will throw an exception.
//
// If pin is passed as an integer, then it is assumed to map to one of the
// internal interrupt sources, and must be in the range 16 thru 22.
//
// All other pin objects go through the pin mapper to come up with one of the
// gpio pins.
//
// extint = pyb.ExtInt(pin, mode, pull, callback)
//
// Valid modes are pyb.ExtInt.IRQ_RISING, pyb.ExtInt.IRQ_FALLING,
// pyb.ExtInt.IRQ_RISING_FALLING, pyb.ExtInt.EVT_RISING,
// pyb.ExtInt.EVT_FALLING, and pyb.ExtInt.EVT_RISING_FALLING.
//
// Only the IRQ_xxx modes have been tested. The EVT_xxx modes have
// something to do with sleep mode and the WFE instruction.
//
// Valid pull values are pyb.GPIO.PULL_UP, pyb.GPIO.PULL_DOWN, pyb.GPIO.PULL_NONE.
//
// extint.line() will return the line number that pin was mapped to.
// extint.disable() can be use to disable the interrupt associated with a given
// exti object. This could be useful for debouncing.
// extint.enable() enables a disabled interrupt
// extint.swint() will allow the callback to be triggered from software.
//
// pyb.ExtInt.regs() will dump the values of the EXTI registers.
//
// There is also a C API, so that drivers which require EXTI interrupt lines
// can also use this code. See extint.h for the available functions and
// usrsw.h for an example of using this.
//
/// \moduleref pyb
/// \class ExtInt - configure I/O pins to interrupt on external events
///
/// There are a total of 22 interrupt lines. 16 of these can come from GPIO pins
/// and the remaining 6 are from internal sources.
///
/// For lines 0 thru 15, a given line can map to the corresponding line from an
/// arbitrary port. So line 0 can map to Px0 where x is A, B, C, ... and
/// line 1 can map to Px1 where x is A, B, C, ...
///
/// def callback(line):
/// print("line =", line)
///
/// Note: ExtInt will automatically configure the gpio line as an input.
///
/// extint = pyb.ExtInt(pin, pyb.ExtInt.IRQ_FALLING, pyb.GPIO.PULL_UP, callback)
///
/// Now every time a falling edge is seen on the X1 pin, the callback will be
/// called. Caution: mechanical pushbuttons have "bounce" and pushing or
/// releasing a switch will often generate multiple edges.
/// See: http://www.eng.utah.edu/~cs5780/debouncing.pdf for a detailed
/// explanation, along with various techniques for debouncing.
///
/// Trying to register 2 callbacks onto the same pin will throw an exception.
///
/// If pin is passed as an integer, then it is assumed to map to one of the
/// internal interrupt sources, and must be in the range 16 thru 22.
///
/// All other pin objects go through the pin mapper to come up with one of the
/// gpio pins.
///
/// extint = pyb.ExtInt(pin, mode, pull, callback)
///
/// Valid modes are pyb.ExtInt.IRQ_RISING, pyb.ExtInt.IRQ_FALLING,
/// pyb.ExtInt.IRQ_RISING_FALLING, pyb.ExtInt.EVT_RISING,
/// pyb.ExtInt.EVT_FALLING, and pyb.ExtInt.EVT_RISING_FALLING.
///
/// Only the IRQ_xxx modes have been tested. The EVT_xxx modes have
/// something to do with sleep mode and the WFE instruction.
///
/// Valid pull values are pyb.GPIO.PULL_UP, pyb.GPIO.PULL_DOWN, pyb.GPIO.PULL_NONE.
///
/// There is also a C API, so that drivers which require EXTI interrupt lines
/// can also use this code. See extint.h for the available functions and
/// usrsw.h for an example of using this.
// TODO Add python method to change callback object.
#define EXTI_OFFSET (EXTI_BASE - PERIPH_BASE)
......@@ -204,29 +198,45 @@ void extint_swint(uint line) {
EXTI->SWIER = (1 << line);
}
/// \method line()
/// Return the line number that the pin is mapped to.
STATIC mp_obj_t extint_obj_line(mp_obj_t self_in) {
extint_obj_t *self = self_in;
return MP_OBJ_NEW_SMALL_INT(self->line);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_line_obj,i extint_obj_line);
/// \method enable()
/// Enable a disabled interrupt.
STATIC mp_obj_t extint_obj_enable(mp_obj_t self_in) {
extint_obj_t *self = self_in;
extint_enable(self->line);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_enable_obj, extint_obj_enable);
/// \method disable()
/// Disable the interrupt associated with the ExtInt object.
/// This could be useful for debouncing.
STATIC mp_obj_t extint_obj_disable(mp_obj_t self_in) {
extint_obj_t *self = self_in;
extint_disable(self->line);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_disable_obj, extint_obj_disable);
/// \method swint()
/// Trigger the callback from software.
STATIC mp_obj_t extint_obj_swint(mp_obj_t self_in) {
extint_obj_t *self = self_in;
extint_swint(self->line);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_swint_obj, extint_obj_swint);
// TODO document as a staticmethod
/// \classmethod regs()
/// Dump the values of the EXTI registers.
STATIC mp_obj_t extint_regs(void) {
printf("EXTI_IMR %08lx\n", EXTI->IMR);
printf("EXTI_EMR %08lx\n", EXTI->EMR);
......@@ -236,9 +246,24 @@ STATIC mp_obj_t extint_regs(void) {
printf("EXTI_PR %08lx\n", EXTI->PR);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(extint_regs_fun_obj, extint_regs);
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(extint_regs_obj, (mp_obj_t)&extint_regs_fun_obj);
// line_obj = pyb.ExtInt(pin, mode, pull, callback)
/// \classmethod \constructor(pin, mode, pull, callback)
/// Create an ExtInt object:
///
/// - `pin` is the pin on which to enable the interrupt (can be a pin object or any valid pin name).
/// - `mode` can be one of:
/// - `ExtInt.IRQ_RISING` - trigger on a rising edge;
/// - `ExtInt.IRQ_FALLING` - trigger on a falling edge;
/// - `ExtInt.IRQ_RISING_FALLING` - trigger on a rising or falling edge.
/// - `pull` can be one of:
/// - `pyb.Pin.PULL_NONE` - no pull up or down resistors;
/// - `pyb.Pin.PULL_UP` - enable the pull-up resistor;
/// - `pyb.Pin.PULL_DOWN` - enable the pull-down resistor.
/// - `callback` is the function to call when the interrupt triggers. The
/// callback function must accept exactly 1 argument, which is the line that
/// triggered the interrupt.
STATIC const mp_arg_t pyb_extint_make_new_args[] = {
{ MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} },
......@@ -268,19 +293,17 @@ STATIC void extint_obj_print(void (*print)(void *env, const char *fmt, ...), voi
print(env, "<ExtInt line=%u>", self->line);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_line_obj, extint_obj_line);
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_enable_obj, extint_obj_enable);
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_disable_obj, extint_obj_disable);
STATIC MP_DEFINE_CONST_FUN_OBJ_1(extint_obj_swint_obj, extint_obj_swint);
STATIC MP_DEFINE_CONST_FUN_OBJ_0(extint_regs_fun_obj, extint_regs);
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(extint_regs_obj, (mp_obj_t)&extint_regs_fun_obj);
STATIC const mp_map_elem_t extint_locals_dict_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR_line), (mp_obj_t)&extint_obj_line_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_enable), (mp_obj_t)&extint_obj_enable_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_disable), (mp_obj_t)&extint_obj_disable_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_swint), (mp_obj_t)&extint_obj_swint_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_regs), (mp_obj_t)&extint_regs_obj },
// class constants
/// \constant IRQ_RISING - interrupt on a rising edge
/// \constant IRQ_FALLING - interrupt on a falling edge
/// \constant IRQ_RISING_FALLING - interrupt on a rising or falling edge
{ MP_OBJ_NEW_QSTR(MP_QSTR_IRQ_RISING), MP_OBJ_NEW_SMALL_INT(GPIO_MODE_IT_RISING) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_IRQ_FALLING), MP_OBJ_NEW_SMALL_INT(GPIO_MODE_IT_FALLING) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_IRQ_RISING_FALLING), MP_OBJ_NEW_SMALL_INT(GPIO_MODE_IT_RISING_FALLING) },
......
"""
Generate documentation for pyboard API from C files.
"""
import os
import argparse
import re
import markdown
# given a list of (name,regex) pairs, find the first one that matches the given line
def re_match_first(regexs, line):
for name, regex in regexs:
match = re.match(regex, line)
if match:
return name, match
return None, None
def makedirs(d):
if not os.path.isdir(d):
os.makedirs(d)
class Lexer:
class LexerError(Exception):
pass
class EOF(Exception):
pass
class Break(Exception):
pass
def __init__(self, file):
self.filename = file
with open(file, 'rt') as f:
line_num = 0
lines = []
for line in f:
line_num += 1
line = line.strip()
if line == '///':
lines.append((line_num, ''))
elif line.startswith('/// '):
lines.append((line_num, line[4:]))
elif len(lines) > 0 and lines[-1][1] is not None:
lines.append((line_num, None))
if len(lines) > 0 and lines[-1][1] is not None:
lines.append((line_num, None))
self.cur_line = 0
self.lines = lines
def opt_break(self):
if len(self.lines) > 0 and self.lines[0][1] is None:
self.lines.pop(0)
def next(self):
if len(self.lines) == 0:
raise Lexer.EOF
else:
l = self.lines.pop(0)
self.cur_line = l[0]
if l[1] is None:
raise Lexer.Break
else:
return l[1]
def error(self, msg):
print('({}:{}) {}'.format(self.filename, self.cur_line, msg))
raise Lexer.LexerError
class DocItem:
def __init__(self):
self.doc = []
def add_doc(self, lex):
try:
while True:
line = lex.next()
if len(line) > 0 or len(self.doc) > 0:
self.doc.append(line)
except Lexer.Break:
pass
def dump(self):
return '\n'.join(self.doc)
class DocConstant(DocItem):
def __init__(self, name, descr):
super().__init__()
self.name = name
self.descr = descr
def dump(self, ctx):
return '{}.{} - {}'.format(ctx, self.name, self.descr)
class DocFunction(DocItem):
def __init__(self, name, args):
super().__init__()
self.name = name
self.args = args
def dump(self, ctx):
if self.name == '\\constructor':
s = '### `{}{}`'.format(ctx, self.args)
elif self.name == '\\call':
s = '### `{}{}`'.format(ctx, self.args)
else:
s = '### `{}.{}{}`'.format(ctx, self.name, self.args)
return s + '\n' + super().dump()
class DocClass(DocItem):
def __init__(self, name, descr):
super().__init__()
self.name = name
self.descr = descr
self.constructors = {}
self.classmethods = {}
self.methods = {}
self.constants = {}
def process_classmethod(self, lex, d):
name = d['id']
if name == '\\constructor':
dict_ = self.constructors
else:
dict_ = self.classmethods
if name in dict_:
lex.error("multiple definition of method '{}'".format(name))
method = dict_[name] = DocFunction(name, d['args'])
method.add_doc(lex)
def process_method(self, lex, d):
name = d['id']
dict_ = self.methods
if name in dict_:
lex.error("multiple definition of method '{}'".format(name))
method = dict_[name] = DocFunction(name, d['args'])
method.add_doc(lex)
def process_constant(self, lex, d):
name = d['id']
if name in self.constants:
lex.error("multiple definition of constant '{}'".format(name))
self.constants[name] = DocConstant(name, d['descr'])
lex.opt_break()
def dump(self):
s = []
s.append('')
s.append('# class {}'.format(self.name))
s.append('')
s.append(super().dump())
if len(self.constructors) > 0:
s.append('')
s.append("## Constructors")
for f in sorted(self.constructors.values(), key=lambda x:x.name):
s.append('')
s.append(f.dump(self.name))
if len(self.classmethods) > 0:
s.append('')
s.append("## Class methods")
for f in sorted(self.classmethods.values(), key=lambda x:x.name):
s.append('')
s.append(f.dump(self.name))
if len(self.methods) > 0:
s.append('')
s.append("## Methods")
for f in sorted(self.methods.values(), key=lambda x:x.name):
s.append('')
s.append(f.dump(self.name.lower()))
if len(self.constants) > 0:
s.append('')
s.append("## Constants")
for c in sorted(self.constants.values(), key=lambda x:x.name):
s.append('')
s.append('`{}`'.format(c.dump(self.name)))
return '\n'.join(s)
class DocModule(DocItem):
def __init__(self, name, descr):
super().__init__()
self.name = name
self.descr = descr
self.functions = {}
self.constants = {}
self.classes = {}
self.cur_class = None
def new_file(self):
self.cur_class = None
def process_function(self, lex, d):
name = d['id']
if name in self.functions:
lex.error("multiple definition of function '{}'".format(name))
function = self.functions[name] = DocFunction(name, d['args'])
function.add_doc(lex)
#def process_classref(self, lex, d):
# name = d['id']
# self.classes[name] = name
# lex.opt_break()
def process_class(self, lex, d):
name = d['id']
if name in self.classes:
lex.error("multiple definition of class '{}'".format(name))
self.cur_class = self.classes[name] = DocClass(name, d['descr'])
self.cur_class.add_doc(lex)
def process_classmethod(self, lex, d):
self.cur_class.process_classmethod(lex, d)
def process_method(self, lex, d):
self.cur_class.process_method(lex, d)
def process_constant(self, lex, d):
self.cur_class.process_constant(lex, d)
def dump(self):
s = []
s.append('# module {}'.format(self.name))
s.append('')
s.append(super().dump())
s.append('')
s.append('## Functions')
for f in sorted(self.functions.values(), key=lambda x:x.name):
s.append('')
s.append(f.dump(self.name))
s.append('')
s.append('## Classes')
for c in sorted(self.classes.values(), key=lambda x:x.name):
s.append('')
s.append('[`{}.{}`]({}/index.html) - {}'.format(self.name, c.name, c.name, c.descr))
return '\n'.join(s)
def write(self, dir):
index = markdown.markdown(self.dump())
with open(os.path.join(dir, 'index.html'), 'wt') as f:
f.write(index)
for c in self.classes.values():
class_dir = os.path.join(dir, c.name)