Added parseActionName
diff --git a/src/composer/__init__.py b/src/composer/__init__.py
index ce04705..a6822ff 100644
--- a/src/composer/__init__.py
+++ b/src/composer/__init__.py
@@ -1,3 +1,8 @@
 __version__ = '0.1.0'
 
-from .composer import *
\ No newline at end of file
+from .composer import Compiler, ComposerError, parse_action_name
+
+_composer = Compiler()
+
+def sequence(*arguments):
+    return _composer.sequence(*arguments)
\ No newline at end of file
diff --git a/src/composer/composer.py b/src/composer/composer.py
index 94b1c2f..fcfbfea 100644
--- a/src/composer/composer.py
+++ b/src/composer/composer.py
@@ -55,56 +55,103 @@
     def __str__(self):
         return json.dumps(self.__dict__, indent=2, default=serialize)
 
-def empty():
-    return _compose('empty', ())
+class Compiler:
+    def empty(self):
+        return self._compose('empty', ())
 
-def seq(*arguments):
-    return _compose('seq', arguments)
+    def seq(self, *arguments):
+        return self._compose('seq', arguments)
 
-def sequence(*arguments):
-    return _compose('sequence', arguments)
+    def sequence(self, *arguments):
+        return self._compose('sequence', arguments)
 
-def action(*arguments):
-    return _compose('action', arguments)
+    def action(self, *arguments):
+        return self._compose('action', arguments)
 
-def task(task):
-    """detect task type and create corresponding composition object"""
-    if task is None:
-        return empty()
+    def task(self, task):
+        """detect task type and create corresponding composition object"""
+        if task is None:
+            return self.empty()
 
-    if isinstance(task, Composition):
-        return task
+        if isinstance(task, Composition):
+            return task
 
-    # if (typeof task === 'function') return this.function(task)
+        # if (typeof task === 'function') return this.function(task)
 
-    if isinstance(task, str): # python3 only
-        return action(task)
+        if isinstance(task, str): # python3 only
+            return self.action(task)
 
-    raise ComposerError('Invalid argument', task)
+        raise ComposerError('Invalid argument', task)
 
-def _compose(type_, arguments):
-    combinator = combinators[type_]
-    skip = len(combinator['args']) if 'args' in combinator else 0
-    if 'components' not in combinator and len(arguments) > skip:
-        raise ComposerError('Too many arguments')
+    def _compose(self, type_, arguments):
+        combinator = combinators[type_]
+        skip = len(combinator['args']) if 'args' in combinator else 0
+        if 'components' not in combinator and len(arguments) > skip:
+            raise ComposerError('Too many arguments')
 
-    composition = Composition(type_)
+        composition = Composition(type_)
 
-    # process named arguments
-    for i in range(skip):
-        arg = combinator['args'][i]
-        argument = arguments[i] if len(arguments) > i else None
+        # process declared arguments
+        for i in range(skip):
+            arg = combinator['args'][i]
+            argument = arguments[i] if len(arguments) > i else None
 
-        if 'type' not in arg:
-            setattr(composition, arg['_'], task(argument))
-        elif arg['type'] == 'value':
-            # if (typeof argument === 'function') throw new ComposerError('Invalid argument', argument)
-            setattr(composition, arg['_'], argument)
-        else:
-            setattr(composition, arg['_'], argument)
+            if 'type' not in arg:
+                setattr(composition, arg['_'], self.task(argument))
+            elif arg['type'] == 'value':
+                # if (typeof argument === 'function') throw new ComposerError('Invalid argument', argument)
+                setattr(composition, arg['_'], argument)
+            else:
+                setattr(composition, arg['_'], argument)
 
-    if 'components' in combinator:
-        setattr(composition, 'components', tuple(map(lambda obj: task(obj), arguments)))
+        if 'components' in combinator:
+            setattr(composition, 'components', tuple(map(lambda obj: self.task(obj), arguments)))
 
+        return composition
 
-    return composition
\ No newline at end of file
+def parse_action_name(name):
+    """
+      Parses a (possibly fully qualified) resource name and validates it. If it's not a fully qualified name,
+      then attempts to qualify it.
+
+      Examples string to namespace, [package/]action name
+        foo => /_/foo
+        pkg/foo => /_/pkg/foo
+        /ns/foo => /ns/foo
+        /ns/pkg/foo => /ns/pkg/foo
+    """
+    name = name.strip()
+    if len(name) == 0:
+        raise ComposerError("Name is not specified")
+
+    delimiter = '/'
+    parts = name.split(delimiter)
+    n = len(parts)
+    leadingSlash = name[0] == delimiter if len(name) > 0 else False
+    # no more than /ns/p/a
+    if n < 1 or n > 4 or (leadingSlash and n == 2) or (not leadingSlash and n == 4):
+        raise ComposerError("Name is not valid")
+
+    # skip leading slash, all parts must be non empty (could tighten this check to match EntityName regex)
+    for part in parts[1:]:
+        if len(part.strip()) == 0:
+            raise ComposerError("Name is not valid")
+
+    newName = delimiter.join(parts)
+    if leadingSlash:
+        return newName
+    elif n < 3:
+        return delimiter+"_"+delimiter+newName
+    else:
+        return delimiter+newName
+
+# class Composer(Compiler):
+#         def action(self, name, options):
+#             """ enhanced action combinator: mangle name, capture code """
+
+#             name = parseActionName(name)
+            # let exec
+
+            # const composition = { type: 'action', name }
+            # if (exec) composition.action = { exec }
+            # return new Composition(composition)
diff --git a/tests/test_composer.py b/tests/test_composer.py
index 15fbc85..b827a7a 100644
--- a/tests/test_composer.py
+++ b/tests/test_composer.py
@@ -1,6 +1,36 @@
 
 import composer
 
+
+def test_parse_action_name():
+    combos = [
+        { "n": "", "s": False, "e": "Name is not specified" },
+        { "n": " ", "s": False, "e": "Name is not specified" },
+        { "n": "/", "s": False, "e": "Name is not valid" },
+        { "n": "//", "s": False, "e": "Name is not valid" },
+        { "n": "/a", "s": False, "e": "Name is not valid" },
+        { "n": "/a/b/c/d", "s": False, "e": "Name is not valid" },
+        { "n": "/a/b/c/d/", "s": False, "e": "Name is not valid" },
+        { "n": "a/b/c/d", "s": False, "e": "Name is not valid" },
+        { "n": "/a/ /b", "s": False, "e": "Name is not valid" },
+        { "n": "a", "e": False, "s": "/_/a" },
+        { "n": "a/b", "e": False, "s": "/_/a/b" },
+        { "n": "a/b/c", "e": False, "s": "/a/b/c" },
+        { "n": "/a/b", "e": False, "s": "/a/b" },
+        { "n": "/a/b/c", "e": False, "s": "/a/b/c" }
+    ]
+    for combo in combos:
+        if combo["s"] is not False:
+            # good cases
+            assert composer.parse_action_name(combo["n"]) == combo["s"]
+        else:
+            # error cases
+            try:
+                composer.parse_action_name(combo["n"])
+                assert False
+            except composer.ComposerError as error:
+                assert error.message == combo["e"]
+
 def test_main():
-    composition = composer.sequence('first', 'second')
+    composition = composer.sequence("first", "second")
     print(composition)