Example #1
0
    def end_list(self, r, line):
        """
        End the current list

        """
        if not self.list_region:
            raise mi_Error("Unbalanced list end.")
        if self.expansion_block:
            raise mi_Error("List closed while expansion block open.")
        self.list_region.terminate()
    def end_list(self, r, line):
        """
        End the current list

        """
        if not self.list_region:
            raise mi_Error("Unbalanced list end.")
        if self.expansion_block:
            raise mi_Error("List closed while expansion block open.")
        self.list_region.terminate()
    def begin_list(self, r, line):
        """
        Process begin list command.

        """
        if self.list_region:
            raise mi_Error("List already open.")
        if self.expansion_block:
            raise mi_Error("List begins during open expansion block.")
        # Create a new list region with the specified delimiter
        self.list_region = List_Region(template=self, delimiter=r.groupdict()["delimiter"])
Example #4
0
    def begin_list(self, r, line):
        """
        Process begin list command.

        """
        if self.list_region:
            raise mi_Error("List already open.")
        if self.expansion_block:
            raise mi_Error("List begins during open expansion block.")
        # Create a new list region with the specified delimiter
        self.list_region = List_Region(template=self,
                                       delimiter=r.groupdict()['delimiter'])
    def begin_expand(self, r, line):
        """
        Start a new expansion block

        """
        if self.expansion_block:
            raise mi_Error("Expansion block already open!")
        try:
            etag_fill = re.findall("\w+", line.split(":")[1])
        except IndexError:
            raise mi_Error("Missing colon in begin expansion.")
        if not etag_fill:
            raise mi_Error("Expansion block has no fill values.")
        self.expansion_block = Expansion_Block(self, etag_fill)
Example #6
0
    def begin_expand(self, r, line):
        """
        Start a new expansion block

        """
        if self.expansion_block:
            raise mi_Error("Expansion block already open!")
        try:
            etag_fill = re.findall('\w+', line.split(':')[1])
        except IndexError:
            raise mi_Error("Missing colon in begin expansion.")
        if not etag_fill:
            raise mi_Error("Expansion block has no fill values.")
        self.expansion_block = Expansion_Block(self, etag_fill)
def parse_property_record( record ):
    """

    """
    if '=' not in record:
        raise mi_Error( _filename, record )
    return { k.strip() : v.strip() for (k, v) in [ t.split('=') for t in record.split(',') ] }
Example #8
0
 def process(self, line):
     for f, p in Template.commands:
         r = p.match(line)
         if r:
             f(self, r, line)
             return
     raise mi_Error('No case matched')
def read_hand_code( hand_code_file ):
    """
    Read all lines between named section headers into the hand_code
    dictionary.

    """
    # Read all the hand code
    try:
        hcfile = open( hand_code_filename )
    except:
        raise mi_File_Error( 'Could not read', hand_code_filename )

    this_section = None
    global hand_code

    for line in hcfile:

        if line.lstrip().startswith(_START_SECTION):
            # Start a new section.  The name is the first non-space word to the right
            # of the start section marker.
            this_section = line.split( _START_SECTION )[1].strip().split()[0].lower()
            if not this_section:
                raise mi_Error( "Bad section header start in hand code file" )
            hand_code[this_section] = []
            continue

        if line.lstrip().startswith(_END_SECTION):
            this_section = None # Now we are between sections
            continue

        if this_section:
            # Inside a section, append line
            hand_code[this_section].append( line )
Example #10
0
def read_hand_code(hand_code_file):
    """
    Read all lines between named section headers into the hand_code
    dictionary.

    """
    # Read all the hand code
    try:
        hcfile = open(hand_code_filename)
    except:
        raise mi_File_Error('Could not read', hand_code_filename)

    this_section = None
    global hand_code

    for line in hcfile:

        if line.lstrip().startswith(_START_SECTION):
            # Start a new section.  The name is the first non-space word to the right
            # of the start section marker.
            this_section = line.split(
                _START_SECTION)[1].strip().split()[0].lower()
            if not this_section:
                raise mi_Error("Bad section header start in hand code file")
            hand_code[this_section] = []
            continue

        if line.lstrip().startswith(_END_SECTION):
            this_section = None  # Now we are between sections
            continue

        if this_section:
            # Inside a section, append line
            hand_code[this_section].append(line)
 def process(self, line):
     for f, p in Template.commands:
         r = p.match(line)
         if r:
             f(self, r, line)
             return
     raise mi_Error("No case matched")
    def unpack_model_lines( self ):
        """
        Populate the model data dictionary

        """
        for n, line in enumerate(self.import_lines.sections['Model']):
            me_type, me_key, mdict = parse( n, line, MODELFILE )
            # model element type, key, and parsed data

            if me_type == 'properties':
                # This is just a set of properties
                self.context['view'][me_type].update( mdict )
                continue

            if me_type not in names:
                # It may be an alias, convert to full name
                try:
                    me_type = alias[me_type]
                except:
                    # No alias found
                    raise mi_Error( "Unknown model element: " + me_type )

            # Promote context if necessary
            while True:
                if me_type in mtax[ self.context['level'] ][IN]:
                    self.stack.append( self.context ) # Dropping down a level
                    break
                if not self.stack:
                    # At top of stack, and model element not recognized
                    raise mi_Error( 'Stack top: Model element not recognized' )
                # Change context to next higher on stack
                self.context = None
                self.context = self.stack.pop()


            # Insert data into current view
            self.context['view'][me_type].update( mdict )

            # Update current context and push it on the stack
            self.context = None # Break reference to stack
            self.context = { 'level':me_type, 'view':self.stack[TOP]['view'][me_type][me_key] }

            # Add empty subfields for next level down
            for ch in mtax[me_type][IN]:
                self.context['view'][ch] = {}
            continue
    def end_expand(self, r, line):
        """
        End the current expansion block

        """
        if not self.expansion_block:
            raise mi_Error("Unbalanced expansion block ending.")
        self.expansion_block.expand()
        self.expansion_block = None
    def begin_cond(self, r, line):
        """
        Create a new conditional segment in the active expansion block

        """
        if not self.expansion_block:
            raise mi_Error("Conditional segment specified outside of any expansion block!")

        self.expansion_block.set_condition(r.groups("condition")[0])
    def end_cond(self, r, line):
        """
        Tell expansion block to stop accepting conditional lines

        """
        if not self.expansion_block:
            raise mi_Error("Conditional segment closed outside of any expansion block!")

        self.expansion_block.close_condition()
Example #16
0
    def end_expand(self, r, line):
        """
        End the current expansion block

        """
        if not self.expansion_block:
            raise mi_Error("Unbalanced expansion block ending.")
        self.expansion_block.expand()
        self.expansion_block = None
Example #17
0
    def __init__( self ):
        self.load_deferrals()
        try:
            self.conn = psycopg2.connect( "dbname=miUML" )
        except:
            raise mi_Error( "Cannot connect to miUML database." )

        self.conn.set_session(
                isolation_level='serializable', readonly=False, autocommit=False
            )
        self.x = self.conn.cursor()
        try: # Set the search path
            self.x.execute( "set search_path to mi, mitrack, miuml, mitype, midom, miclass, "
                    "mirel, miform, mirrid, mistate, mipoly" )
            self.conn.commit()
        except:
            raise mi_Error( "Cannot set the db search_path." )
        self.x.close()
Example #18
0
def parse_property_record(record):
    """

    """
    if '=' not in record:
        raise mi_Error(_filename, record)
    return {
        k.strip(): v.strip()
        for (k, v) in [t.split('=') for t in record.split(',')]
    }
Example #19
0
    def end_cond(self, r, line):
        """
        Tell expansion block to stop accepting conditional lines

        """
        if not self.expansion_block:
            raise mi_Error(
                "Conditional segment closed outside of any expansion block!")

        self.expansion_block.close_condition()
Example #20
0
    def begin_cond(self, r, line):
        """
        Create a new conditional segment in the active expansion block

        """
        if not self.expansion_block:
            raise mi_Error(
                "Conditional segment specified outside of any expansion block!"
            )

        self.expansion_block.set_condition(r.groups('condition')[0])
Example #21
0
 def __init__(self, name, file_name, target_name_pattern):
     self.name = name
     self.lines = []
     self.file_name = os.path.join("Resources", file_name)
     codegen_path = os.getenv("micodegen", False)
     if not codegen_path:
         raise mi_Error("micodegen env variable not set.")
     self.target_name_pattern = target_name_pattern
     self.target = None
     self.list_region = None
     self.expansion_block = None
 def __init__(self, name, file_name, target_name_pattern):
     self.name = name
     self.lines = []
     self.file_name = os.path.join("Resources", file_name)
     codegen_path = os.getenv("micodegen", False)
     if not codegen_path:
         raise mi_Error("micodegen env variable not set.")
     self.target_name_pattern = target_name_pattern
     self.target = None
     self.list_region = None
     self.expansion_block = None
    def parse( self, method_name, parsed_expr ):
        """
        Given a lightly parsed expression, invoke a corresponding method
        to parse that data a bit deeper and buffer any forward
        references or dependencies.

        """
        # Verify that the member function is defined on this class
        if method_name not in self.__class__.__dict__.keys():
            print(">> Parse: bad method_name")
            pdb.set_trace()
            raise mi_Error( "No parse function for expression: " + method_name )

        # Invoke it, passing along the parsed expression data
        eval( 'self.' + method_name )( parsed_expr )
        # These expression functions are defined below
        print(">> Expr:" + method_name + " meta-parsed")
Example #24
0
def splice_hand_code(gen_file):
    """
    Splice (insert) each section at the correspondng section marker
    in the generated code file.

    """
    try:
        gen_file = open(gen_filename)
    except:
        raise mi_File_Error("Could not open", gen_filename)

    global hand_code
    target_lines = []
    inside_splice_section = False

    for line in gen_file:

        if line.lstrip().startswith(_START_SECTION):
            target_lines.append(line)
            this_section = line.split(
                _START_SECTION)[1].strip().split()[0].lower()
            if not this_section:
                raise mi_Error("Bad section header start in gen code file")
            if this_section in hand_code:
                inside_splice_section = True
                target_lines.extend(hand_code[this_section])
                del hand_code[this_section]  # So it won't be reinserted
            continue

        if line.lstrip().startswith(_END_SECTION):
            inside_splice_section = False
            target_lines.append(line)
            continue

        if not inside_splice_section:
            target_lines.append(line)

    try:
        # Write out the target lines buffer
        with open(gen_filename, 'w') as gen_file:
            gen_file.write(''.join(target_lines))
    except:
        raise mi_File_Error("Could not write", gen_filename)
def splice_hand_code( gen_file ):
    """
    Splice (insert) each section at the correspondng section marker
    in the generated code file.

    """
    try:
        gen_file = open( gen_filename )
    except:
        raise mi_File_Error( "Could not open", gen_filename )

    global hand_code
    target_lines = []
    inside_splice_section = False

    for line in gen_file:

        if line.lstrip().startswith(_START_SECTION):
            target_lines.append( line )
            this_section = line.split( _START_SECTION )[1].strip().split()[0].lower()
            if not this_section:
                raise mi_Error( "Bad section header start in gen code file" )
            if this_section in hand_code:
                inside_splice_section = True
                target_lines.extend( hand_code[this_section] )
                del hand_code[this_section] # So it won't be reinserted
            continue

        if line.lstrip().startswith(_END_SECTION):
            inside_splice_section = False
            target_lines.append(line)
            continue

        if not inside_splice_section:
            target_lines.append( line )

    try:
        # Write out the target lines buffer
        with open( gen_filename, 'w' ) as gen_file:
            gen_file.write( ''.join( target_lines ) )
    except:
        raise mi_File_Error( "Could not write", gen_filename )
    def new_ind_attr( self, attr_data ):
        """
        If an identifier string is specified, parse it.  Convert the data type to
        the correct API variable name and reject if type is unknown.

        """
        # Create any required identifier attributes
        if attr_data['id']:
            self.parse_id( attr_data )

        # Fill in / convert / validate the type name
        attr_type = attr_data['type']
        if not attr_type:
            # Assume it is the same as the attribute name
            attr_type = attr_data['name']
        # Change it to a variable name style with lower case and underscores
        attr_type = attr_type.replace(" ", "_").lower()
        if attr_type not in API_Type:
            raise mi_Error("Unknown data type specified: " + attr_type )

        # Change the extracted value to the correct type name
        attr_data['type'] = attr_type
 def __init__(self, subsys_dir, file_name):
     project_dir = getenv(_PROJECT_HOME_DIR_ENV_VAR, False)
     if not project_dir:
         raise mi_Error("{} env var not set".format(_PROJECT_HOME_DIR_ENV_VAR))
     self.name = path.join(project_dir, subsys_dir, file_name)
     self.lines = []
Example #28
0
            # There's a hand code dir in this subdir, cd into it
            try:
                os.chdir(hc_dir)
            except:
                # Maybe subdirs is stale?
                print("Could not access dir: {}  Skipping.".format(subsys +
                                                                   os.sep +
                                                                   hc_dir))
                continue

            # Assert: In project_dir/subsystem_dir/knit_dir

            for hand_code_filename in glob.glob(_GLOB_SUFFIX):
                print("      Knitting: " + hand_code_filename)

                # The target gen'd file will have the same name in the parent dir
                gen_filename = os.pardir + os.sep + hand_code_filename

                # Read all the hand code and break into sections
                read_hand_code(hand_code_filename)

                # Splice in each section into the designated location in the gen file
                splice_hand_code(gen_filename)

            try:
                os.chdir(os.pardir)  # pop back up into the subsys dir
            except:
                raise mi_Error(
                    "Could not return to parent dir: {}  Abort.".format(
                        os.abspath(subsys)))
    def new_ref_attr( self, attr_data ):
        """
        According to the miUML metatmodel, a Referential Attribute participates in
        one or more References.  A Relationship is formalized by one or more
        References.

        This meanst that a new referential attribute parsed here may participate in
        multiple References.  Each of these References must be added to the references
        dictionary structured as shown:

        references -> { rnum: <reference>, ... }
        <reference> -> [
            'from_attr':from_attr, 'to_attr':to_attr,
            'from_class':from_class, 'to_class':to_class, constrained:<boolean>
            ]

        This data will be used during the relationship construction phase after all
        new class and new ind attr commands are added to the DB Pop Script.

        The incoming reference dictionary has the following keys:
            'from_attr', 'to_attrs', 'id', 'rnum', 'constrained'

        Most of these values are fully parsed by the extracting regex, but 'to_attrs'
        may contain multiple to attributes, so these must be parsed out to yield possibly
        many references, one for each destination attribute.

        Finally, we request an identifier attribute if id membership is specified as
        a referential attribute can participate in an identifier just like an independent
        attribute.
        
        """
        # Save for quick access in to_attr loop
        rnum = attr_data['rnum']
        constrained = attr_data['constrained']
        from_attr = attr_data['from_attr']
        id_member = attr_data['id']
        from_class = Context_Parameter['class']
        this_subsys = Context_Parameter['subsys']
        this_domain = Context_Parameter['domain']

        # Create a new attr_data set for this relationship
        if rnum not in self.references:
            self.references[rnum] = []

        # Parse to_attrs
        to_attrs = attr_data['to_attrs'].split(", ")
        for to_attr in to_attrs:
            ref_rec = {
                    'from_attr':from_attr,
                    'subsystem':this_subsys,
                    'from_class':from_class,
                    'constrained':constrained
                }
            if SUBSYS_REF in to_attr:
                to_subsys, to_attr = to_attr.split( SUBSYS_REF )
                if this_subsys not in self.subsys_dependencies:
                    self.subsys_dependencies[this_subsys] = set( )
                self.subsys_dependencies[this_subsys].add( to_subsys )
            try:
                ref_rec['to_class'], ref_rec['to_attr'] = to_attr.split( '.' )
            except ValueError:
                raise mi_Error( "Bad to attr reference in referential attribute: " + to_attr )
            self.references[rnum].append( ref_rec )

        # If ref attr participates in one or more ids, parse id data
        if attr_data['id']:
            attr_data = {'name':attr_data['from_attr'], 'id':attr_data['id']}
            self.parse_id( attr_data )
    def parse_ui_args( self, op, arg_text ):
        """
        Parses the args portion of a single text command line to produce an arg_map.
        This is a dictionary of arg:value and switch:True pairs.  The arg_map can be
        later supplied to the op's designated implementation function.

        """
        arg_map = {} # the parsed data goes here
        arg_text = arg_text.strip() # line is unparsed portion of arg_text
        fset = set() # For grouping comparison

        # Build the arg map
        match_end = 0 # total length of matched groups for line chopping
        while arg_text:
            a, v, pattern, arg_text = self.extract_arg_item( arg_text )

            if pattern == 'value': # matches arg value pattern, ex: -s domain
                # validate( a ) # validate_uiarg(a) or validate_apparg(a)
                if a not in self.ui_cmd[op]['syntax']:
                    # Arg not defined for this op
                    raise mi_Syntax_Error( self.ui_cmd[op]['help'] )

                if self.ui_cmd[op]['syntax'][a]['action'] != 'store':
                    # Arg does not take a value for this op
                    raise mi_Syntax_Error( self.ui_cmd[op]['help'] )

            elif pattern == 'flag': # matches flat pattern, ex: -f
                if a not in self.ui_cmd[op]['syntax']:
                    # Arg not defined for this op
                    raise mi_Syntax_Error( self.ui_cmd[op]['help'] )

                arg_spec = self.ui_cmd[op]['syntax'][a] # for brevity

                if arg_spec['action'] == 'store':
                    try:
                        v = arg_spec['default']
                    except KeyError:
                        # Value should have been specified since there was no default
                        raise mi_Syntax_Error( self.ui_cmd[op]['help'] )
                elif arg_spec['action'] == 'switch':
                    v = True
                else: # action must be 'store' or 'switch', fatal error
                    raise mi_Error( 'No action defined for flag: ' + a )
            else:
                # A regex was matched, but it's not valid for a ui command
                # For example, a comma separated list is not an acceptable ui arg
                raise mi_Syntax_Error( self.ui_cmd[op]['help'] )

            # Add arg_map entry a, v, will overwrite any duplicate
            arg_map[ self.ui_cmd[op]['syntax'][a]['var'] ] = v.strip() if v else None

            # Update group
            fset.add(a)


        # Ensure that each required flag has been provided
        # and has a value.  Switches will always carry a True value.
        for flag in self.ui_cmd[op]['syntax']:
            if 'required' not in self.ui_cmd[op]['syntax'][flag]:
                continue # Don't worry about optional flags
            if self.ui_cmd[op]['syntax'][flag]['var'] not in arg_map:
                raise mi_Syntax_Error( self.ui_cmd[op]['help'] )

        # Enforce grouping rules
        if not arg_map and not self.ui_cmd[op].get('grouping'):
            # No groups and no args
            return arg_map

        group_found = False
        # Find 

        for group in self.ui_cmd[op]['grouping']:
            if fset == set(group): # Order doesn't matter, so we use sets
                group_found = True
                break
        if not group_found:
            # There is no legal grouping that corresponds to what we parsed
            raise mi_Syntax_Error( self.ui_cmd[op]['help'] )
        
        return arg_map
Example #31
0
 def __init__( self, subsys_dir, file_name ):
     project_dir = getenv( _PROJECT_HOME_DIR_ENV_VAR, False )
     if not project_dir:
         raise mi_Error("{} env var not set".format( _PROJECT_HOME_DIR_ENV_VAR ) )
     self.name = path.join( project_dir, subsys_dir, file_name )
     self.lines = []
Example #32
0
    def build_commands( self, section ):
        """
        Parses command data read from a structured mi file to produce a dictionary
        mapping user commands and arguments to API calls and parameters.

        """
        # Each type of line that will be processed is defined by a matching regex

        # Subject of a command, ex: 'attr, attribute, a : name' (never indented)
        # yields a list of subject names and an optional colon delimiter followed
        # by the type of the local identifying attribute
        subject_line = re.compile( r'^(?P<names>\w[\w,\s]*)(:\s*(?P<type>\w+))?' )

        # Operation that can be applied to the subject.
        # Yields the name of the op a colon delimiter and the base name of the
        # corresponding API call.
        # example: '    new : new_ind_attr' (must be indented)
        op_line = re.compile( r'^\s+\w+\s*:\s*\w+\s*$')

        # Arguments (one or more) prefixed with an purpose code such as
        # f for focus, m for modify or o for output.  This regex identifies
        # an argument line and its purpose, but does not extract the arg elements
        arg_line = re.compile( r'^\s+(?P<purpose>[fmo])>' )

        # Arg patterns - extract argument data
        # Various patterns are successivly applied to an argument line to match and
        # extract the content of one or more argument records
        arg_patterns = {
            # Focus argument patterns
            'focus':[
                # match [d|domain:domain]
                re.compile( r'(?P<optional>\[)(?P<ui>\w+)\|(?P<app>\w+):(?P<scope>\w+)\]' ),
                # match   cl|client:domain
                re.compile( r'(?P<ui>\w+)\|(?P<app>\w+):(?P<scope>\w+)' ),
                # match [d:domain]
                re.compile( r'(?P<optional>\[)(?P<ui>\w+):(?P<scope>\w+)\]' ),
                # match   client:domain
                re.compile( r'(?P<ui>\w+):(?P<scope>\w+)' ),
                # match   domain
                re.compile( r'(?P<scope>\w+)' )
            ],
            # Modify argument patterns
            'mod':[
                # match [ph|phrase:text]
                re.compile( r'(?P<optional>\[)(?P<ui>\w+)\|(?P<app>\w+):(?P<type>\w+)\]' ),
                #re.compile( r'(?P<ui>\w+)\|(?P<app>\w+):(?P<type>\w+)=(?P<default>\w+)' ),
                # match ph|phrase:text
                re.compile( r'(?P<ui>\w+)\|(?P<app>\w+):(?P<type>\w+)' ),
                # match [ph:text...]
                re.compile( r'(?P<optional>\[)(?P<ui>\w+):(?P<type>\w+)(?P<list>\.\.\.)\]' ),
                # match [ph:text]
                re.compile( r'(?P<optional>\[)(?P<ui>\w+):(?P<type>\w+)\]' ),
                # match ph:text...
                re.compile( r'(?P<ui>\w+):(?P<type>\w+)(?P<list>\.\.\.)' ),
                # match ph:text
                re.compile( r'(?P<ui>\w+):(?P<type>\w+)' )
            ],
            # Output (returned) argument pattern
            'out':[ re.compile( r'(?P<o_param>\w+)' ) ]
        }
        
        pcode = { 'f':'focus', 'm':'mod', 'o':'out' }

        # help string pattern
        scoped_type = re.compile( r'@(\w+)@' )

        this_subject = ""
        this_op = ""
        state = "start"
        
        for line in section:
            # extract_sections has removed any blank lines
            # print( "LINE: [{}]".format(line))

            r = subject_line.match( line )
            if r:
                if state == 'subject':
                    raise mi_Error( "Subject [{}] has no ops defined.".format(this_subject) )
                if state != 'start':
                    del op

                subject = { 'ops':{} } # Temporary record to be added to command dict

                # line format is: name_list [: type]
                # Names are csv on the left side of the colon

                if not r.group('names'):
                    raise mi_Error( 'No names specified for subject.' )

                subject['names'] = re.findall( r'\w+', r.group('names') )
                self.subjects.update(subject['names'])
                this_subject = subject['names'][0] # first name is used officially

                # If it's there, get the type on the right side of the colon
                if r.group('type'):
                    subject['scope'] = r.group('type')

                self.commands[this_subject] = subject # ops to be added in next case
                del subject # so we can re-use it later without changing current subject
                state = 'subject' # Next case knows we are inside a subject
                continue # subject line match

            if op_line.match( line ):
                if state == 'start':
                    raise mi_Error( 'Operation found before any subjects.' )
                if state == 'operation':
                    # pp( op )
                    del op

                # Split op name : api_call on the colon
                this_op = line.split(':')[0].strip() # left side of colon
                self.ops.update([this_op]) # add to convenience set
                op = self.commands[this_subject]['ops'][this_op] = { 'args':{} }
                op['api_call'] = line.split(':')[1].strip() # right side of colon
                op['help'] = "{} {} ".format( this_op, this_subject ) # arg names appended later
                state = 'operation' # Next case knows we are inside an operation
                continue # opline match

            r = arg_line.match( line ) # to get the arg_type in re group
            if r:
                if state != 'operation':
                    raise mi_Error( 'Args outside of operation.' )

                # discard [mfo]> prefix and split to list on commas, stripping spaces
                args_rec = re.findall( r'\[?\w[\w\|:\.]*\]?', line.split('>')[1] ) # right of x>
                purpose = pcode[r.group('purpose')] # map x> to name of purpose

                for a in args_rec:
                    d = next(
                        # Apply each regex defined for the attr purpose (focus, mod or out)
                        # The first match creates dictionary d from the pattern
                        ( p.match(a).groupdict() for p in arg_patterns[purpose] if p.match(a) ),
                            False # returned if there is no match
                    )
                    if not d:
                        # Must be malformed input
                        raise mi_Error(
                            'No pattern matched for arg: [{}] on line\n{}.'.format(a, line)
                        )

                    d['optional'] = True if d.get('optional') else False
                    d['list'] = True if d.get('list') else False

                    if d.get('o_param'):
                        if op.get('olist'):
                            # append the o_param
                            op['olist'].append( d.get('o_param') )
                        else:
                            # start a new list with o_param as first item
                            op['olist'] = [ d.get('o_param') ]
                        continue

                    if d.get('ui'):
                        arg_name = d.pop('ui') # Name of the command line argument
                    else:
                        try:
                            arg_name = d['scope']
                        except KeyError:
                            raise mi_Error( 'Scope missing from non-ui arg [{}].'.format(d) )
                    d['purpose'] = purpose
                    if purpose == 'focus':
                        # Type will be that of the local identifier defined for some
                        # subject which we may not have processed yet.  So we save a forward
                        # reference to be filled in with an actual type later.
                        arg_type = "@{}@".format( d['scope'] ) # Forward reference
                    else:
                        # Otherwise, just save the specified type
                        arg_type = "<{}>".format( d['type'] )

                    op['args'][arg_name] = d # add argument to the argset for the current op
                    if "boolean" in arg_type:
                        h = "-{}".format( arg_name )
                    else:
                        h = "-{} {}".format( arg_name, arg_type )
                    if d['list']:
                        h = h + "[, ...]"
                    if d['optional']:
                        h = "[" + h + "] "
                    else:
                        h += " "
                    op['help'] += h
                continue # argline match

            # Error if line is not a subject, operation or arg set
            raise mi_Error('Unrecgonized command in API def file.')

        # All lines processed
        if state == 'subject':
            raise mi_Error( 'Trailing subject [{}] has no operations.'.format(this_subject)  )

        # Wherever a scoped subject is referenced in a help string
        # replace it with a type appropriate for naming that subject

        for s in self.commands: # Each subject
            for op in self.commands[s]['ops']: # Each op

                # Grab the help string which may have one or more scope subject references
                # in @ brackets such as @class@.  Now we must replace each with the
                # corresponding type name found under the subject['scope'] key
                # For example, a class subject is defined by a 'name' type
                # a rel subject would be defined by a 'nominal' type, on the other hand

                h = self.commands[s]['ops'][op]['help'] # h is the help string

                # Now replace each found occurence of <subject> (if any) with
                # the correpsonding type found at self.commands[<subject>]['scope']

                # The sub() function searches h using the scoped_type regex defined up at
                # the top of this function yielding zero or more matches (m) fed into the
                # lambda function.  The lambda simply strips off the @ brackets, 
                # looks up and returns the type for substitution.
                try:
                    h = re.sub(
                        scoped_type,
                        lambda m: "<{}>".format(self.commands[m.group(1).strip('@')]['scope']
                    ), h )
                except KeyError as e:
                    raise mi_Error( 'Cannot resolve scope type for {}'.format(e) )

                # Replace the old help string with the resolved version, and we're done!
                self.commands[s]['ops'][op]['help'] = h.strip()
            # There's a hand code dir in this subdir, cd into it
            try:
                os.chdir( hc_dir )
            except:
                # Maybe subdirs is stale?
                print( "Could not access dir: {}  Skipping.".format(
                    subsys + os.sep + hc_dir
                ) )
                continue

            # Assert: In project_dir/subsystem_dir/knit_dir

            for hand_code_filename in glob.glob( _GLOB_SUFFIX ):
                print("      Knitting: " + hand_code_filename )

                # The target gen'd file will have the same name in the parent dir
                gen_filename = os.pardir + os.sep + hand_code_filename

                # Read all the hand code and break into sections
                read_hand_code( hand_code_filename )

                # Splice in each section into the designated location in the gen file
                splice_hand_code( gen_filename )
        
            try:
                os.chdir( os.pardir ) # pop back up into the subsys dir
            except:
                raise mi_Error( "Could not return to parent dir: {}  Abort.".format(
                    os.abspath(subsys)
                ) )