def compile(src, dirs, verbose=False, srs=None, datasources_cfg=None, user_styles=[], scale=1):
    """ Compile a Cascadenik MML file, returning a cascadenik.output.Map object.
    
        Parameters:
        
          src:
            Path to .mml file, or raw .mml file content.
          
          dirs:
            Object with directory names in 'cache', 'output', and 'source' attributes.
            dirs.source is expected to be fully-qualified, e.g. "http://example.com"
            or "file:///home/example".
        
        Keyword Parameters:
        
          verbose:
            If True, debugging information will be printed to stderr.
        
          srs:
            Target spatial reference system for the compiled stylesheet.
            If provided, overrides default map srs in the .mml file.
        
          datasources_cfg:
            If a file or URL, uses the config to override datasources or parameters
            (i.e. postgis_dbname) defined in the map's canonical <DataSourcesConfig>
            entities.  This is most useful in development, whereby one redefines
            individual datasources, connection parameters, and/or local paths.
        
          user_styles:
            A optional list of files or URLs, that override styles defined in
            the map source. These are evaluated in order, with declarations from
            later styles overriding those from earlier styles.
        
          scale:
            Scale value for output map, 2 doubles the size for high-res displays.
    """
    global VERBOSE

    if verbose:
        VERBOSE = True
        sys.stderr.write('\n')
    
    msg('Targeting mapnik version: %s | %s' % (MAPNIK_VERSION, MAPNIK_VERSION_STR))
        
    if posixpath.exists(src):
        doc = ElementTree.parse(src)
        map_el = doc.getroot()
    else:
        try:
            # guessing src is a literal XML string?
            map_el = ElementTree.fromstring(src)
    
        except:
            if not (src[:7] in ('http://', 'https:/', 'file://')):
                src = "file://" + src
            try:
                doc = ElementTree.parse(urllib.urlopen(src))
            except IOError, e:
                raise IOError('%s: %s' % (e,src))
            map_el = doc.getroot()

    expand_source_declarations(map_el, dirs, datasources_cfg)
    declarations = extract_declarations(map_el, dirs, scale, user_styles)
    
    # a list of layers and a sequential ID generator
    layers, ids = [], (i for i in xrange(1, 999999))


    # Handle base datasources
    # http://trac.mapnik.org/changeset/574
    datasource_templates = {}
    for base_el in map_el:
        if base_el.tag != 'Datasource':
            continue
        datasource_templates[base_el.get('name')] = dict(((p.get('name'),p.text) for p in base_el.findall('Parameter')))
    
    for layer_el in map_el.findall('Layer'):
    
        # nevermind with this one
        if layer_el.get('status', None) in ('off', '0', 0):
            continue

        # build up a map of Parameters for this Layer
        datasource_params = dict((p.get('name'),p.text) for p in layer_el.find('Datasource').findall('Parameter'))

        base = layer_el.find('Datasource').get('base')
        if base:
            datasource_params.update(datasource_templates[base])

        if datasource_params.get('table'):
            # remove line breaks from possible SQL, using a possibly-unsafe regexp
            # that simply blows away anything that looks like it might be a SQL comment.
            # http://trac.mapnik.org/ticket/173
            if not MAPNIK_VERSION >= 601:
                sql = datasource_params.get('table')
                sql = compile(r'--.*$', MULTILINE).sub('', sql)
                sql = sql.replace('\r', ' ').replace('\n', ' ')
                datasource_params['table'] = sql

        elif datasource_params.get('file') is not None:
            # make sure we localize any remote files
            file_param = datasource_params.get('file')

            if datasource_params.get('type') == 'shape':
                # handle a local shapefile or fetch a remote, zipped shapefile
                msg('Handling shapefile datasource...')
                file_param = localize_shapefile(file_param, dirs)

                # TODO - support datasource reprojection to make map srs
                # TODO - support automatically indexing shapefiles

            else: # ogr,raster, gdal, sqlite
                # attempt to generically handle other file based datasources
                msg('Handling generic datasource...')
                file_param = localize_file_datasource(file_param, dirs)

            msg("Localized path = %s" % un_posix(file_param))
            datasource_params['file'] = un_posix(file_param)

            # TODO - consider custom support for other mapnik datasources:
            # sqlite, oracle, osm, kismet, gdal, raster, rasterlite

        layer_declarations = get_applicable_declarations(layer_el, declarations)
        
        # a list of styles
        styles = []
        
        if datasource_params.get('type', None) == 'gdal':
            styles.append(output.Style('raster style %d' % ids.next(),
                                       get_raster_rules(layer_declarations)))
    
        else:
            styles.append(output.Style('polygon style %d' % ids.next(),
                                       get_polygon_rules(layer_declarations)))
    
            styles.append(output.Style('polygon pattern style %d' % ids.next(),
                                       get_polygon_pattern_rules(layer_declarations, dirs)))
    
            styles.append(output.Style('line style %d' % ids.next(),
                                       get_line_rules(layer_declarations)))
    
            styles.append(output.Style('line pattern style %d' % ids.next(),
                                       get_line_pattern_rules(layer_declarations, dirs)))
    
            for (shield_name, shield_rules) in get_shield_rule_groups(layer_declarations, dirs).items():
                styles.append(output.Style('shield style %d (%s)' % (ids.next(), shield_name), shield_rules))
    
            for (text_name, text_rules) in get_text_rule_groups(layer_declarations).items():
                styles.append(output.Style('text style %d (%s)' % (ids.next(), text_name), text_rules))
    
            styles.append(output.Style('point style %d' % ids.next(),
                                       get_point_rules(layer_declarations, dirs)))
                                   
        styles = [s for s in styles if s.rules]
        
        if styles:
            datasource = output.Datasource(**datasource_params)
            
            layer = output.Layer('layer %d' % ids.next(),
                                 datasource, styles,
                                 layer_el.get('srs', None),
                                 layer_el.get('min_zoom', None) and int(layer_el.get('min_zoom')) or None,
                                 layer_el.get('max_zoom', None) and int(layer_el.get('max_zoom')) or None)
    
            layers.append(layer)
    
    map_attrs = get_map_attributes(get_applicable_declarations(map_el, declarations))
    
    # if a target srs is profiled, override whatever is in mml
    if srs is not None:
        map_el.set('srs', srs)
    
    return output.Map(map_el.attrib.get('srs', None), layers, **map_attrs)