docs: 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)