def _load_actions_and_plans(cls, path, plan=None, depth=0): import os list = os.listdir(path) if depth == 0: cls.plans._objects = qlist() cls.actions._objects = qlist() # iterate through files and directories in actions directory for d in list: # treat directories as plans and files as actions if os.path.isdir(path + '/' + d) is True: # load plans p = cls.plans.load(path + '/' + d, plan) # recursively load actions and plans cls._load_actions_and_plans(path + '/' + d, p, depth=depth+1) else: # ignore .plans file, it is processed when loading a plans if d != '.plan': # load actions a = cls.actions.load(path + '/' + d, plan)
def __init__(self): super(Plan, self).__init__() # persisted attrs self._type_ = type # non-persisted attrs self._parent = None self._actions = qlist(listener=self._actions_listener) self._plans = qlist(listener=self._plans_listener)
def __init__(self): # persistance attrs self.ignore_changes = False self.persisted = {} self.id = None # persisted attrs self._name_ = None self._tags_ = qlist(listener=self._tags_listener, before_add=self._tags_before_add) self._targets_ = qlist(listener=self._targets_listener) # explicit persistance - not in json self._docs = qlist(listener=self._docs_listener)
def _load_logs(cls): cls.logentries._objects = qlist() import os list_ = os.listdir('/'.join([cls.life_prj_path, LOGS_DIR])) # iterate through files in logs directory for d in list_: # don't load .changes log files, they are only logged for debug info if not d.endswith('.changes') and os.path.isfile('/'.join([cls.life_prj_path, LOGS_DIR, d])): with open('/'.join([cls.life_prj_path, LOGS_DIR, d])) as logfile: # load log entry for line in logfile.readlines(): # load line into LogEntry instance logentry = cls.logentries.load(line, d) # resolve model reference model = cls.actions.get(name=logentry.obj) if model is None: model = cls.plans.get(name=logentry.obj) # relate logentry to models if model is not None: logentry.obj = model model.history.append(logentry) parent = model.parent while parent is not None: parent.history.append(logentry) parent = parent.parent
def get_test_obj(listener=None): """Returns a qlist for testing :returns: Returns a qlist for testing :rtype: hoomanpy.qlist """ ql = qlist(listener=listener) for i in range(0, 10): ql.append(DummyModel('Object #{}'.format(i), i + 1)) ql[2].status = "inactive" ql[4].status = "inactive" ql[6].status = "inactive" ql[3].status = "in progress" ql[5].status = "in progress" ql[7].status = "in progress" ql[0].tags.extend(['pop', 'rock', 'groovy', 'music']) ql[1].tags.extend(['pop', 'rock', 'groovy', 'music']) ql[2].tags.extend(['pop', 'rock', 'groovy', 'music']) ql[3].tags.extend(['country', 'live', 'depressing', 'music']) ql[4].tags.extend(['country', 'live', 'depressing', 'music']) ql[5].tags.extend(['country', 'live', 'depressing', 'music']) ql[6].tags.extend(['folk', 'indie', 'hipster', 'music']) ql[7].tags.extend(['folk', 'indie', 'hipster', 'music']) ql[8].tags.extend(['folk', 'indie', 'hipster', 'music']) return ql
def setup(cls, life_path, stdout=None, logfilename=None): # set up data access cls.life_prj_path = life_path if logfilename is None: import datetime cls.log_file_name = datetime.datetime.today().strftime('%Y-%m-%d') cls.stdout = stdout m.dal = DataAccess # connect model data access classes cls.actions = ActionManager cls.plans = PlanManager cls.logentries = LogEntryManager cls.targets = TargetManager cls.changed = [] cls.changed_log = qlist() # load models cls.ignore_changes = True cls._load_actions_and_plans('/'.join([cls.life_prj_path, ACTIONS_DIR])) cls._load_logs() cls.ignore_changes = False
def purge_object_from_logs(cls, obj): """Remove all log entries for an action and clear the ``action.history`` property.""" # get distinct list of log file names from the action history log_filenames = cls.get_log_file_names(obj.history) # no log history if log_filenames is None: return # loop over a sliced copy of self.logentries to avoid skipping items # due to the internal indexing of the iteration for logentry in cls.logentries.all()[:]: if logentry.obj is obj: cls.logentries.remove(logentry) if logentry.type == 3 and isinstance(obj, m.Action) and obj.parent is not None: obj.parent.history.remove(logentry) # read lines from each logfile and then rewrite the # logfile, omitting lines that are for this action for log_filename in log_filenames: with open('/'.join([cls.life_prj_path, LOGS_DIR, log_filename]), 'r') as logfile: lines = logfile.readlines() with open('/'.join([cls.life_prj_path, LOGS_DIR, log_filename]), 'w') as logfile: for line in lines: if line.find('"obj": "' + obj.name + '"') < 0: logfile.write(line) # reset object's history property obj.history = qlist()
def get_test_obj(listener=None): """Returns a qlist for testing :returns: Returns a qlist for testing :rtype: hoomanpy.qlist """ ql = qlist(listener=listener) for i in range(0, 10): ql.append(DummyModel("Object #{}".format(i), i + 1)) ql[2].status = "inactive" ql[4].status = "inactive" ql[6].status = "inactive" ql[3].status = "in progress" ql[5].status = "in progress" ql[7].status = "in progress" ql[0].tags.extend(["pop", "rock", "groovy", "music"]) ql[1].tags.extend(["pop", "rock", "groovy", "music"]) ql[2].tags.extend(["pop", "rock", "groovy", "music"]) ql[3].tags.extend(["country", "live", "depressing", "music"]) ql[4].tags.extend(["country", "live", "depressing", "music"]) ql[5].tags.extend(["country", "live", "depressing", "music"]) ql[6].tags.extend(["folk", "indie", "hipster", "music"]) ql[7].tags.extend(["folk", "indie", "hipster", "music"]) ql[8].tags.extend(["folk", "indie", "hipster", "music"]) return ql
def get_actions(self, status='all', plan=None, tags=None): """Return a list of actions. :param status: Status to include (active, inactive, future, or all). :type status: str :param plan: Plan to filter actions to. :type plan: models.Plan :param tags: Tags to filter the list of actions by. :type tags: str, list<str> :returns: A list of actions. :rtype: hoomanpy.qlist """ if plan is not None: list_ = plan.actions else: list_ = qlist() list_.extend(dal.actions.all()) list_.extend(dal.plans.filter(type='option', actions__count__gt=0)) if status in ('active', 'inactive', 'future'): list_ = list_.filter(status=status) if tags is not None: if not hasattr(tags, '__iter__'): tags = [tags] list_ = list_.filter(tags__in=tags) return list_
def get_tags(self, status='all'): """Return a distinct, alphetically-sorted list of tags assigned. :param status: 'all', 'active', 'inactive', or 'notinuse' to Return tags assigned to actions and plans that have the given status. 'old' will return tags that are assigned only to inactive actions and plans. :type status: str :return: A distinct list of tags. :rtype: hoomanpy.qlist """ if status == 'all': tags = dal.actions.all().distinct('tags') tags.extend(dal.plans.all().distinct('tags')) elif status == 'active': tags = dal.actions.active().distinct('tags') tags.extend(dal.plans.active().distinct('tags')) elif status == 'inactive': old = self.get_tags('inactive') new = self.get_tags('active') a = set(old) b = set(new) c = a - b tags = qlist() tags.extend(c) else: raise AttributeError( "{} is not a supported status argument. Must be 'all', 'active', or 'inactive'" ) return tags.sort().distinct()
def get_tags(self, status='all'): """Return a distinct, alphetically-sorted list of tags assigned. :param status: 'all', 'active', 'inactive', or 'notinuse' to Return tags assigned to actions and plans that have the given status. 'old' will return tags that are assigned only to inactive actions and plans. :type status: str :return: A distinct list of tags. :rtype: hoomanpy.qlist """ if status == 'all': tags = dal.actions.all().distinct('tags') tags.extend(dal.plans.all().distinct('tags')) elif status == 'active': tags = dal.actions.active().distinct('tags') tags.extend(dal.plans.active().distinct('tags')) elif status == 'inactive': old = self.get_tags('inactive') new = self.get_tags('active') a = set(old) b = set(new) c = a - b tags = qlist() tags.extend(c) else: raise AttributeError("{} is not a supported status argument. Must be 'all', 'active', or 'inactive'") return tags.sort().distinct()
def __init__(self): super(ActionableModel, self).__init__() # non-persisted attrs self.history = qlist() self._on_logged = None # persisted attrs self._status_ = 'active' self._progress_ = 0 self._minutes_ = 0 self._order_ = 0
def _load_from_file(path): jsondict = {} data = {} data['tags'] = qlist() data['targets'] = qlist() data['docs'] = qlist() # extract tags and props from header from json import loads with open(path) as f: for i, line in enumerate(f): if i == 0: if line.strip() != '': jsondict = loads(line) if isinstance(jsondict, dict): for key, value in jsondict.iteritems(): if key == 'tags': tags = value.split() if len(tags) > 0: data[key].extend(tags) elif key == 'targets': for t in value: t = loads(t) target = m.Target.load(t['freq'], t['starts'], t['measure'], period_target=t['period_target'], interval=t['interval'], met_after=t['met_after']) data[key].append(target) else: data[key] = value else: docs = line.replace('\n', '').rstrip() if len(docs) != 0 or len(data['docs']) != 0: data['docs'].append(docs) data['persisted'] = jsondict return data
def translate_action_or_plan(hooman_input, context=None): """Translate string to a model reference. :param hooman_input: String input :type hooman_input: str """ # index translation if context.index_mode in ['actions', 'plans']: translated, model = translate_index_to_model(hooman_input, context) if translated: return True, model # model id translation translated, model = translate_id_to_action(hooman_input, context) if translated: return True, model translated, model = translate_id_to_plan(hooman_input, context) if translated: return True, model # model name translation # build list of matches matches = qlist() action_matches = context.controller.get_actions().filter( name__contains=hooman_input) plan_matches = context.controller.get_plans().filter( name__contains=hooman_input) if action_matches is not None: matches.extend(action_matches) if plan_matches is not None: matches.extend(plan_matches) if len(matches) == 1: return True, matches[0] # todo: implement Match Certainty to notify user we aren't sure this is what they wanted elif len(matches) > 1: closest_match = None len_diff = 1000 for item in matches: if len_diff > len(item.name) - len(hooman_input): len_diff = len(item.name) - len(hooman_input) closest_match = item if len_diff < 2: return True, closest_match return False, None
def translate_action_or_plan(hooman_input, context=None): """Translate string to a model reference. :param hooman_input: String input :type hooman_input: str """ # index translation if context.index_mode in ["actions", "plans"]: translated, model = translate_index_to_model(hooman_input, context) if translated: return True, model # model id translation translated, model = translate_id_to_action(hooman_input, context) if translated: return True, model translated, model = translate_id_to_plan(hooman_input, context) if translated: return True, model # model name translation # build list of matches matches = qlist() action_matches = context.controller.get_actions().filter(name__contains=hooman_input) plan_matches = context.controller.get_plans().filter(name__contains=hooman_input) if action_matches is not None: matches.extend(action_matches) if plan_matches is not None: matches.extend(plan_matches) if len(matches) == 1: return True, matches[0] # todo: implement Match Certainty to notify user we aren't sure this is what they wanted elif len(matches) > 1: closest_match = None len_diff = 1000 for item in matches: if len_diff > len(item.name) - len(hooman_input): len_diff = len(item.name) - len(hooman_input) closest_match = item if len_diff < 2: return True, closest_match return False, None
def translate_action_plan_target_or_tag(hooman_input, context=None): """Translate string to a model reference. :param hooman_input: String input :type hooman_input: str """ # index translation if context.index_mode in ['actions', 'plans', 'targets', 'tags']: translated, model = translate_index_to_model(hooman_input, context) if translated: return True, model # model id translation translated, model = translate_id_to_action(hooman_input, context) if translated: return True, model translated, model = translate_id_to_plan(hooman_input, context) if translated: return True, model # tag name translation if hooman_input.startswith('#'): # tag format tags = context.controller.get_tags().filter( name__contains=hooman_input) if len(tags) == 1: return True, tags[0] # model name translation # build list of matches matches = qlist() action_matches = context.controller.get_actions().filter( name__contains=hooman_input) plan_matches = context.controller.get_plans().filter( name__contains=hooman_input) if action_matches is not None: matches.extend(action_matches) if plan_matches is not None: matches.extend(plan_matches) if len(matches) == 1: return True, matches[0] return False, None
def translate_action_plan_target_or_tag(hooman_input, context=None): """Translate string to a model reference. :param hooman_input: String input :type hooman_input: str """ # index translation if context.index_mode in ["actions", "plans", "targets", "tags"]: translated, model = translate_index_to_model(hooman_input, context) if translated: return True, model # model id translation translated, model = translate_id_to_action(hooman_input, context) if translated: return True, model translated, model = translate_id_to_plan(hooman_input, context) if translated: return True, model # tag name translation if hooman_input.startswith("#"): # tag format tags = context.controller.get_tags().filter(name__contains=hooman_input) if len(tags) == 1: return True, tags[0] # model name translation # build list of matches matches = qlist() action_matches = context.controller.get_actions().filter(name__contains=hooman_input) plan_matches = context.controller.get_plans().filter(name__contains=hooman_input) if action_matches is not None: matches.extend(action_matches) if plan_matches is not None: matches.extend(plan_matches) if len(matches) == 1: return True, matches[0] return False, None
class ModelManager(object): _objects = qlist() @classmethod def all(cls): return cls._objects @classmethod def filter(cls, **kwargs): return cls._objects.filter(**kwargs) @classmethod def distinct(cls, attribute=None): return cls._objects.distinct(attribute=attribute) @classmethod def get(cls, **kwargs): return cls._objects.get(**kwargs) @classmethod def append(cls, obj): cls._objects.append(obj) @classmethod def remove(cls, obj): cls._objects.remove(obj) @classmethod def extend(cls, iterable): cls._objects.extend(iterable) @staticmethod def _load_from_file(path): jsondict = {} data = {} data['tags'] = qlist() data['targets'] = qlist() data['docs'] = qlist() # extract tags and props from header from json import loads with open(path) as f: for i, line in enumerate(f): if i == 0: if line.strip() != '': jsondict = loads(line) if isinstance(jsondict, dict): for key, value in jsondict.iteritems(): if key == 'tags': tags = value.split() if len(tags) > 0: data[key].extend(tags) elif key == 'targets': for t in value: t = loads(t) target = m.Target.load(t['freq'], t['starts'], t['measure'], period_target=t['period_target'], interval=t['interval'], met_after=t['met_after']) data[key].append(target) else: data[key] = value else: docs = line.replace('\n', '').rstrip() if len(docs) != 0 or len(data['docs']) != 0: data['docs'].append(docs) data['persisted'] = jsondict return data
def inactive(cls): inactive_list = qlist() for plan in cls._objects: if not cls.has_active_children(plan): inactive_list.append(plan) return inactive_list