Exemple #1
0
    def _schedule_block(self, block, start, alloc, par):
        logger.debug(f"_schedule_block: scheduling block in {block.loc}: "
                     f"start={start}, alloc={alloc}, par={par}")

        alloc = block.alloc or alloc
        par = block.parallel or par
        latest = start

        if block.goal_name is None:
            for b in block.blocks:
                last = self._schedule_block(b, start, alloc, par)
                latest = max(latest or last, last)
                if block.seq:
                    start = last
        else:
            assert not block.blocks, "block with goals should have no subblocks"
            goal = self.net.name_to_goal.get(block.goal_name)
            error_if(goal is None, block.loc,
                     f"goal '{block.goal_name}' not found in plan")
            goal_finish = self._schedule_goal(goal, start, alloc, par)
            latest = max(latest, goal_finish)

        if block.deadline is not None and latest > block.deadline:
            warn(
                "Failed to schedule block at {block.loc} before deadline {block.deadline}"
            )

        return latest
Exemple #2
0
 def update_name_to_goal(g):
   self.name_to_goal[g.name] = g
   if g.id is not None:
     other_goal = self.name_to_goal.get(g.id, None)
     error_if(other_goal is not None and other_goal.name != g.name,
              f"goals '{other_goal.name}' and '{g.name}' use the same id '{g.id}'")
     self.name_to_goal[g.id] = g
Exemple #3
0
def open_file(filename):
    """Open file with appropriate reader."""
    if sys.platform == 'cygwin':
        rc = os.system(f'cygstart {filename}')
    elif sys.platform.startswith('win'):
        rc = os.system(f'explorer {filename}')
    else:
        rc = os.system(f'xdg-open {filename}')
    error_if(rc != 0, f"failed to open pdf file '{filename}'")
Exemple #4
0
def read_date(s, loc):
    """Parse date duration e.g. "2020-01-10"."""
    # TODO: allow shorter formats (e.g. 'Jan 10')
    # but what if someone uses this in 2020?!
    m = re.search(r'^\s*([^-\s]*-[^-\s]*)(-[^-\s]*)?\s*(.*)', s)
    error_if(m is None, loc, f"failed to parse date: {s}")
    date_str = m.group(1)
    # If day is omitted, consider first day
    date_str += m.group(2) or '-01'
    return datetime.datetime.strptime(date_str, '%Y-%m-%d').date(), m.group(3)
Exemple #5
0
 def get_resources(self, names):
   """Returns resources that match a set of team/resource names."""
   resources = []
   for name in names:
     team = self.teams_map.get(name)
     if team is not None:
       for rc in team.members:
         if rc not in resources:
           resources.append(rc)
     else:
       rc = self.members_map.get(name)
       error_if(rc is None, f"resource '{name}' not defined")
       if rc not in resources:
         resources.append(rc)
   resources = sorted(resources, key=lambda rc: rc.name)
   return resources
Exemple #6
0
 def _recompute(self):
   self.members_map = {m.name : m for m in self.members}
   self.teams_map = {t.name : t for t in self.teams}
   if 'all' in self.teams_map:
     error(self.teams_map['all'].loc, "predefined goal 'all' overriden")
   self.teams_map['all'] = Team('all', self.members, self.loc)
   # Resolve resources
   for team in self.teams:
     error_if(team.name in self.members_map, team.loc,
              f"team '{team.name}' clashes with developer '{team.name}'")
     for i, name in enumerate(team.members):
       if not isinstance(name, str):
         continue
       m = self.members_map.get(name)
       error_if(m is None, team.loc, f"no member with name '{name}'")
       team.members[i] = m
Exemple #7
0
def read_effort(s, loc):
    """Parse effort estimate e.g. "1h" or "3d"."""
    m = re.search(r'^\s*([0-9]+(?:\.[0-9]+)?)([hdwmy])\s*(.*)', s)
    error_if(m is None, f"failed to parse effort: {s}")
    d = float(m.group(1))
    spec = m.group(2)
    rest = m.group(3)
    if spec == 'd':
        d *= 8
    elif spec == 'w':
        d *= 5 * 8  # Work week
    elif spec == 'm':
        d *= 22 * 8  # Work month
    elif spec == 'y':
        d *= 12 * 22 * 8  # Work year
    d = int(round(d))
    return d, rest
Exemple #8
0
    def parse_checks(self, g, goal_offset):
        while True:
            l = self.lex.next_if(LexemeType.CHECK)
            if l is None:
                return
            logger.debug(f"parse_checks: new check: {l}")

            check_offset, status, text = l.data
            error_if(check_offset != goal_offset, l.loc,
                     "check is not properly nested")
            error_if(status not in {'X', 'F', ''}, l.loc,
                     f"unexpected check status: '{status}'")

            check = G.Condition(text, status, l.loc)
            g.add_check(check)

            if self.lex.next_if(LexemeType.ATTR_START) is not None:
                a = self.parse_attrs()
                check.add_attrs(a, l.loc)
Exemple #9
0
    def parse(self, W):
        net_loc = project_loc = sched_loc = Location()

        root_goals = []
        root_blocks = []
        while True:
            l = self.lex.peek()
            if l is None:
                break
            logger.debug(f"parse: next lexeme: {l}")
            if l.type == LexemeType.GOAL:
                error_if(l.data[0] != 0, l.loc,
                         f"root goal '{l.data[1]}' must be left-adjusted")
                goal = self.parse_goal(l.data[0], None, False)
                if not net_loc:
                    net_loc = goal.loc
                root_goals.append(goal)
            elif l.type == LexemeType.PRJ_ATTR:
                if not project_loc:
                    project_loc = l.loc
                self.parse_project_attr()
            elif l.type == LexemeType.SCHED:
                error_if(l.data[0] != 0, l.loc,
                         "root block must be left-adjusted")
                block = self.parse_sched_block(l.data[0])
                if not sched_loc:
                    sched_loc = block.loc
                root_blocks.append(block)
            elif l.type == LexemeType.EOF:
                break
            else:
                # TODO: anonymous goals
                error(l.loc, f"unexpected lexeme: '{l.text}'")

        net = G.Net(root_goals, W, net_loc)

        prj = project.Project(project_loc)
        prj.add_attrs(self.project_attrs)

        sched = schedule.SchedPlan(root_blocks, sched_loc)

        return net, prj, sched
Exemple #10
0
    def parse_goal(self, offset, other_goal, is_pred, allow_empty=False):
        logger.debug(f"parse_goal: start lex: {self.lex.peek()}")
        loc = self.lex.loc()
        goal_name, goal_attrs = self.maybe_parse_goal_decl(offset)

        if goal_name is None:
            if not allow_empty:
                return None
            # The infamous PERT dummy goals
            goal = self._make_dummy_goal(loc)
            was_defined = False
            logger.debug("parse_goal: creating dummy goal")
        else:
            goal = self.names.get(goal_name)
            if goal is None:
                was_defined = False
                goal = self.names[goal_name] = G.Goal(goal_name, loc)
                logger.debug(f"parse_goal: parsed new goal: {goal.name}")
            else:
                was_defined = goal.defined
                logger.debug(f"parse_goal: parsed existing goal: {goal.name}")

        if goal_attrs:
            error_if(
                was_defined, loc,
                f"duplicate definition of goal '{goal.name}' "
                f"(previous definition was in {goal.loc})")
            goal.add_attrs(goal_attrs, loc)

        # TODO: Gaperton's examples contain interwined checks and deps
        self.parse_checks(goal, offset)

        self.parse_subgoals(goal, offset)

        if not was_defined and (goal.checks or goal_attrs or goal.children):
            goal.defined = True
            if other_goal is not None and is_pred:
                other_goal.add_child(goal)

        return goal
Exemple #11
0
def _create_wbs_iterative(net, ids):
  user_iters = list(filter(lambda i: i is not None, net.iter_to_goals.keys()))
  error_if(not user_iters, "no iterations defined in plan")

  user_iters.sort()
  last_iter = (user_iters[-1] + 1) if user_iters else 0

  tasks = []

  for i in user_iters + [None]:
    i_num = last_iter if i is None else i

    task = Task(f'iter_{i_num}', f'Iteration {i_num}', None)
    task.depends.add(f'iter_{i_num - 1}')
    tasks.append(task)

    for g in net.iter_to_goals[i]:
      if not _is_goal_ignored(g):
        t = _create_goal_task(g, task, ids)
        task.subtasks.append(t)

  return WBS(tasks)
Exemple #12
0
def read_float(s, loc):
    """Parse float number."""
    m = re.search(r'^\s*([0-9]+(\.[0-9]+)?)(.*)', s)
    error_if(m is None, loc, f"failed to parse float: {s}")
    return float(m.group(1)), m.group(3)
Exemple #13
0
 def expect_one_value(loc, name, vals):
     error_if(
         len(vals) != 1, loc,
         f"too many values for attribute '{name}': " + ', '.join(vals))
Exemple #14
0
 def set_completion_date(self, goal, d):
     error_if(self.is_completed(goal),
              f"goal '{goal.name}' scheduled more than once")
     self.goals[goal.name] = GoalInfo(goal.name, d)
Exemple #15
0
 def add_attrs(self, attrs, loc):
   attrs = add_common_attrs(loc, self, attrs)
   error_if(attrs, loc, "unknown condition attribute(s): " + ', '.join(attrs))
Exemple #16
0
 def enter(g, path=path):  # pylint: disable=dangerous-default-value
   error_if(g.name in path, "found a cycle: %s" % '\n  '.join(path))
   path.append(g.name)
Exemple #17
0
 def set_duration(self, act, iv, alloc):
     error_if(self.is_done(act),
              f"activity '{act.name}' scheduled more than once")
     self.acts[act.name] = ActivityInfo(act, iv, alloc)