def parse_project_attr(self): l = self.lex.next() name = l.data attr_loc = l.loc self.lex.expect('=') rhs = [] while True: l = self.lex.expect('LIST_ELT') rhs.append(l.data) if not self.lex.next_if(','): break def expect_one_value(loc, name, vals): error_if( len(vals) != 1, loc, f"too many values for attribute '{name}': " + ', '.join(vals)) if name in {'name', 'tracker_link', 'pr_link'}: expect_one_value(l.loc, name, rhs) val = rhs[0] elif name in {'start', 'finish'}: expect_one_value(l.loc, name, rhs) val, _ = PA.read_date(rhs[0], attr_loc) elif name == 'members': val = [] for rc_info in rhs: if not M.match(r'([A-Za-z][A-Za-z0-9_]*)\s*(?:\(([^\)]*)\))?', rc_info): error(attr_loc, f"failed to parse resource declaration: {rc_info}") rc_name, attrs = M.groups() rc = project.Resource(rc_name, attr_loc) if attrs: rc.add_attrs(re.split(r'\s*,\s*', attrs), attr_loc) val.append(rc) elif name == 'teams': val = [] for team_info in rhs: if not M.match(r'\s*([A-Za-z][A-Za-z0-9_]*)\s*\(([^)]*)\)$', team_info): error(attr_loc, f"invalid team declaration: {team_info}") team_name = M.group(1) rc_names = re.split(r'\s*,\s*', M.group(2).strip()) val.append(project.Team(team_name, rc_names, attr_loc)) elif name == 'holidays': val = [] for s in rhs: iv = PA.read_date2(s, attr_loc) val.append(iv) else: error(attr_loc, f"unknown project attribute: {name}") self.project_attrs[name] = val
def read_alloc(a, loc): """Parse allocation directive e.g. "@dev1/dev2 (dev3)".""" aa = a.split('(') if len(aa) > 2 or not M.search(r'^@\s*(.*)', aa[0]): error(loc, f"unexpected allocation syntax: {a}") alloc = M.group(1).strip().split('/') if len(aa) <= 1: real_alloc = [] else: if not M.search(r'^([^)]*)\)', a): error(loc, f"unexpected allocation syntax: {a}") real_alloc = M.group(1).strip().split('/') return alloc, real_alloc
def read_fraction(s, loc): if M.search(r'^[0-9.]+$', s): return float(s) if M.search(r'^([0-9]+)%$', s): return int(M.group(1)) / 100 error(loc, f"unexpected fraction syntax: {s}") raise ValueError("silly Pylint fails to understand NoReturn")
def add_common_attrs(loc, obj, attrs): """Adds attributes that are common for goals, checks and activities.""" other_attrs = [] for a in attrs: if M.search(r'^([A-Za-z][A-Za-z0-9_]*)\s*(.*)', a): k = M.group(1).strip() #v = M.group(2).strip() if k == 'task': obj.tracker.tasks = set(M.group(1).split('/')) continue if k == 'PR': obj.tracker.prs = set(M.group(1).split('/')) continue other_attrs.append(a) return other_attrs
def add_attrs(self, attrs, loc): for a in attrs: if a[0].isdigit(): self.efficiency = P.read_fraction(a, loc) elif M.search(r'vacations?\s+(.*)', a): duration = P.read_date2(M.group(1), loc) self.vacations.append(duration) else: error(loc, f"unexpected resource attribute: {a}")
def add_attrs(self, attrs, loc): attrs = add_common_attrs(loc, self, attrs) for a in attrs: if re.search(r'^[0-9.]+[hdwmy]', a): # Parse estimate self.effort = PA.read_eta(a, loc) continue if a.startswith('@'): self.alloc, self.real_alloc = PA.read_alloc(a, loc) continue # TODO: specify in effort attribute? if M.search(r'^[0-9]{4}-', a): self.duration = PA.read_date2(a, loc) continue if M.match(r'^id\s+(.*)', a): self.id = M.group(1) if M.match(r'over\s+(\S+)\s+(.*)', a): other_id = M.group(1) overlap, a = PA.read_float(M.group(2), loc) if a == '%': overlap /= 100 self.overlaps[other_id] = overlap if a.startswith('||'): self.parallel = PA.read_par(a) continue if not M.search(r'^([a-z_0-9]+)\s*(.*)', a): error(loc, f"failed to parse attribute: {a}") k = M.group(1).strip() #v = M.group(2).strip() if k == 'global': self.globl = True continue error(loc, f"unknown activity attribute: '{k}'")
def read_eta(s, loc): """Parse effort estimate e.g. "1h", "1h-3d" or "1h-3d (1d)".""" min, rest = read_effort(s, loc) max = min if rest and rest[0] == '-': max, rest = read_effort(rest[1:], loc) real = None completion = 0 if M.search(r'^\s*\((.*)\)\s*$', rest): for a in re.split(r'\s*,\s*', M.group(1)): if re.search(r'^[0-9.]+[hdwmy]', a): real, _ = read_effort(a, loc) elif M.search(r'^([0-9]+)%', a): completion = float(M.group(1)) / 100 else: error(loc, f"unknown ETA attribute: {a}") return ETA(min, max, real, completion)
def add_attrs(self, attrs, loc): attrs = add_common_attrs(loc, self, attrs) for a in attrs: if a.find('!') == 0: try: self.prio = Priority(int(a[1:])) except ValueError: error(loc, f"invalid priority value: {a}") continue if a.find('?') == 0: try: self.risk = Risk(int(a[1:])) except ValueError: error(loc, f"invalid risk value: {a}") continue if M.search(r'^I[0-9]+$', a): self.iter = int(a[1:]) continue if M.search(r'^[0-9]{4}-', a): self.completion_date, _ = PA.read_date(a, loc) continue if not M.search(r'^([a-z_0-9]+)\s*(.*)', a): error(loc, f"failed to parse goal attribute: {a}") k = M.group(1).strip() v = M.group(2).strip() if k == 'deadline': self.deadline, _ = PA.read_date(v, loc) continue if k == 'id': self.id = v continue error(loc, f"unknown goal attribute '{k}'")
def add_attrs(self, attrs, loc): for a in attrs: if a.startswith('@'): self.alloc, _ = PA.read_alloc(a, loc) continue if M.search(r'^[0-9]{4}-', a): self.duration = PA.read_date2(a, loc) continue if a.startswith('||'): self.parallel = PA.read_par(a) continue if not M.search(r'^([a-z_0-9]+)\s*(.*)', a): error(loc, f"failed to parse block attribute: {a}") k = M.group(1).strip() v = M.group(2).strip() if k == 'deadline': self.deadline, _ = PA.read_date(v, loc) continue error(loc, f"unknown block attribute '{k}'")
def next_internal(self): if self.line == '': # File exhausted type = LexemeType.EOF data = text = '' elif self.mode == LexerMode.ATTR: nest = 0 self.line = self.line.lstrip() if self.line[0] == ',': type = ',' i = 1 else: type = LexemeType.LIST_ELT for i, c in enumerate(self.line): if c == ',' and not nest: break if c == '(': nest += 1 elif c == ')': nest -= 1 else: i += 1 self.mode = LexerMode.NORMAL text = self.line[:i] data = text.rstrip() self.line = self.line[i:] else: data = None if M.match(r'( *)\|([<>])-', self.line): type = LexemeType.LARROW if M.group( 2) == '<' else LexemeType.RARROW data = len(M.group(1)) elif M.match(r'( *)\|\[([^\]]*)\]\s*(.*?)(?=(//|$))', self.line): type = LexemeType.CHECK data = len(M.group(1)), M.group(2), M.group(3).strip() elif M.match(r'^(\s*)(--|\|\|)\s*', self.line): type = LexemeType.SCHED data = len(M.group(1)), M.group(2) elif M.match(r'( *)\|(.*?)(?=//|$)', self.line): type = LexemeType.GOAL data = len(M.group(1)), M.group(2).strip() elif M.match(r'\s*//', self.line): type = LexemeType.ATTR_START self.mode = LexerMode.ATTR elif M.match(r'([A-Za-z][A-Za-z0-9_]*)(?=\s*=)', self.line): type = LexemeType.PRJ_ATTR data = M.group(1) elif M.match(r'\s*=\s*', self.line): type = LexemeType.ASSIGN self.mode = LexerMode.ATTR else: error(self._loc(), f"unexpected syntax: {self.line}") self.line = self.line[len(M.group(0)):] text = M.group(0) self.lexemes.append(PA.Lexeme(type, data, text, self._loc()))