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"])
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)
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(',') ] }
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 )
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()
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()
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(',')] }
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()
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 __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")
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 = []
# 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
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 = []
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) ) )