"""Utility functions for tag databases."""
import abc
import re

try:
    from importlib import import_module
except ImportError:  # python < 2.7 compatibility
    from django.utils.importlib import import_module


class TaggedSeries(object):
  @classmethod
  def parse(cls, path):
    m = re.match('^(.+?)((?:;[^;!=]+=[^;]*)*)$', path)
    if not m:
      raise Exception('Cannot parse path')

    metric = m.group(1)
    tagstring = m.group(2)

    tags = {}

    while True:
      m = re.match(';([^;]+)=([^;]*)(;.*|$)', tagstring)
      if m is None:
        break

      tags[m.group(1)] = m.group(2)

      tagstring = m.group(3)
      if tagstring == '':
        break

    tags['name'] = metric

    return cls(metric, tags)

  def __init__(self, metric, tags, id=None):
    self.metric = metric
    self.tags = tags
    self.id = id

  def format(self):
    return self.metric + ''.join(sorted([
      ';' + tag + '=' + value
      for tag, value in self.tags.items()
      if tag != 'name'
    ]))


class BaseTagDB(object):
  __metaclass__ = abc.ABCMeta

  def __init__(self):
    """Initialize the tag db."""
    pass

  @abc.abstractmethod
  def find_series(self, tags):
    """Find series by tag"""
    pass

  @abc.abstractmethod
  def get_series(self, path):
    """Get series by path"""
    pass

  @abc.abstractmethod
  def list_tags(self):
    """List defined tags"""
    pass

  @abc.abstractmethod
  def get_tag(self, tag):
    """Get details of a particular tag"""
    pass

  @abc.abstractmethod
  def list_values(self, tag):
    """List values for a particular tag"""
    pass

  @abc.abstractmethod
  def tag_series(self, series):
    """Enter series into database and return canonical series name"""
    pass

  @abc.abstractmethod
  def del_series(self, series):
    """Remove series from database"""
    pass

  def parse(self, path):
    return TaggedSeries.parse(path)


def get_tagdb(tagdb_path):
    module_name, class_name = tagdb_path.rsplit('.', 1)
    module = import_module(module_name)
    return getattr(module, class_name)()

@@ -50,18 +50,8 @@
  abc
-%0Aimport re
 %0A%0Atr
@@ -273,58 +273,60 @@
     
-m = re.match('%5E(.+?)((?:;%5B%5E;!=%5D+=%5B%5E;%5D*)*)$', path)
+segments = path.split(';')%0A%0A    metric = segments%5B0%5D
 %0A   
@@ -334,16 +334,21 @@
 if not m
+etric
 :%0A      
@@ -381,31 +381,31 @@
 rse path
-')%0A%0A   
+ %25s, no
  metric 
 = m.grou
@@ -400,277 +400,258 @@
 ric 
-= m.group(1)%0A    tagstring = m.group(2)%0A%0A    tags = %7B%7D%0A%0A    while True:%0A      m = re.match(';(%5B%5E;%5D+)=(%5B%5E;%5D*)(;.*%7C$)', tagstring)%0A      if m is None:%0A        break%0A%0A      tags%5Bm.group(1)%5D = m.group(2)%0A%0A      tagstring = m.group(3)
+found' %25 path)%0A%0A    tags = %7B%7D%0A%0A    for segment in segments%5B1:%5D:%0A      tag = segment.split('=', 1)%0A      if len(tag) != 2 or not tag%5B0%5D:%0A        raise Exception('Cannot parse path %25s, invalid segment %25s' %25 (path, segment))%0A
 %0A      
-if 
 tags
-tring == '':%0A        break
+%5Btag%5B0%5D%5D = tag%5B1%5D
 %0A%0A  

simplify metric path parsing