def do_p_edit(self, line): """Edit a project. p_edit <project name>""" project = dbutils.getOrCreateProject(line, createIfNeeded=False) if not project: raise YokadiException("Project does not exist.") # Create project line projectLine = parseutils.createLine(project.name, "", project.getKeywordDict()) # Edit line = tui.editLine(projectLine) # Update project projectName, garbage, keywordDict = parseutils.parseLine(line) if garbage: raise BadUsageException("Cannot parse line, got garbage (%s)" % garbage) if not dbutils.createMissingKeywords(keywordDict.keys()): return try: project.name = projectName except DuplicateEntryError: raise YokadiException( "A project named %s already exists. Please find another name" % projectName) project.setKeywordDict(keywordDict)
def do_t_add_keywords(self, line): """Add keywords to an existing task t_add_keywords <id> <@keyword1> <@keyword2>[=<value>]... """ tokens = parseutils.simplifySpaces(line).split(" ", 1) if len(tokens) < 2: raise YokadiException( "You should give at least two arguments: <task id> <keyword>") task = dbutils.getTaskFromId(tokens[0]) garbage, keywordFilters = parseutils.extractKeywords(tokens[1]) newKwDict = parseutils.keywordFiltersToDict(keywordFilters) if garbage: raise YokadiException( "Cannot parse line, got garbage (%s). Maybe you forgot to add @ before keyword ?" % garbage) if not dbutils.createMissingKeywords(list(newKwDict.keys())): # User cancel keyword creation return kwDict = task.getKeywordDict() kwDict.update(newKwDict) task.setKeywordDict(kwDict) self.session.merge(task) self.session.commit()
def parseOneWordName(line): """Parse line, check it is a one word project name and return it @return: the name """ line = line.strip() if " " in line: raise YokadiException("Name cannot contain spaces") if not line: raise YokadiException("Name cannot be empty") return line
def getKeywordFromName(name): """Returns a keyword from its name, which may start with "@" raises a YokadiException if not found @param name: the keyword name @return: The keyword""" if not name: raise YokadiException("No keyword supplied") if name.startswith("@"): name = name[1:] lst = list(Keyword.selectBy(name=name)) if len(lst) == 0: raise YokadiException("No keyword named '%s' found" % name) return lst[0]
def getKeywordFromName(name): """Returns a keyword from its name, which may start with "@" raises a YokadiException if not found @param name: the keyword name @return: The keyword""" session = db.getSession() if not name: raise YokadiException("No keyword supplied") if name.startswith("@"): name = name[1:] lst = session.query(Keyword).filter_by(name=name).all() if len(lst) == 0: raise YokadiException("No keyword named '%s' found" % name) return lst[0]
def do_t_filter(self, line): """Define permanent keyword filter used by t_list Ex.: - t_filter @work (filter all task that have the "work" keyword) - t_filter none (remove filter)""" # TODO: add completion if not line: raise YokadiException( "You must give keyword as argument or 'none' to reset filter") if parseutils.simplifySpaces(line).lower() == "none": self.kFilters = [] self.pFilter = "" self.prompt = "yokadi> " else: projectName, keywordFilters = parseutils.extractKeywords(line) self.kFilters = keywordFilters self.pFilter = projectName prompt = "y" if self.pFilter: prompt += " %s" % projectName if self.kFilters: parseutils.warnIfKeywordDoesNotExist(self.kFilters) prompt += " %s" % (" ".join([str(k) for k in keywordFilters])) self.prompt = "%s> " % prompt
def do_t_describe(self, line): """Starts an editor to enter a longer description of a task. t_describe <id>""" def updateDescription(description): if self.cryptoMgr.isEncrypted(task.title): task.description = self.cryptoMgr.encrypt(description) else: task.description = description task = self.getTaskFromId(line) try: if self.cryptoMgr.isEncrypted(task.title): # As title is encrypted, we assume description will be encrypted as well self.cryptoMgr.force_decrypt = True # Decryption must be turned on to edit description = tui.editText( self.cryptoMgr.decrypt(task.description), onChanged=updateDescription, lockManager=dbutils.TaskLockManager(task), prefix="yokadi-%s-%s-" % (task.project, task.title)) except Exception as e: raise YokadiException(e) updateDescription(description) self.session.merge(task) self.session.commit()
def addTask(projectName, title, keywordDict=None, interactive=True): """Adds a task based on title and keywordDict. @param projectName: name of project as a string @param title: task title as a string @param keywordDict: dictionary of keywords (name : value) @param interactive: Ask user before creating project (this is the default) @type interactive: Bool @returns : Task instance on success, None if cancelled.""" if keywordDict is None: keywordDict = {} # Create missing keywords if not createMissingKeywords(keywordDict.keys(), interactive=interactive): return None # Create missing project project = getOrCreateProject(projectName, interactive=interactive) if not project: return None # Create task try: task = Task(creationDate=datetime.now(), project=project, title=title, description="", status="new") task.setKeywordDict(keywordDict) except DuplicateEntryError: raise YokadiException("A task named %s already exists in this project. Please find another name" % title) return task
def _processVTodo(self, vTodo): if vTodo["UID"] in self.newTask: # This is a recent new task but remote ical calendar tool is not # aware of new Yokadi UID. Update it here to avoid duplicate new tasks print "update UID to avoid duplicate task" vTodo["UID"] = TASK_UID % self.newTask[vTodo["UID"]] if vTodo["UID"].startswith(UID_PREFIX): # This is a yokadi Task. if vTodo["LAST-MODIFIED"].dt > vTodo["CREATED"].dt: # Task has been modified print "Modified task: %s" % vTodo["UID"] result = TASK_RE.match(vTodo["UID"]) if result: id = result.group(1) task = dbutils.getTaskFromId(id) print "Task found in yokadi db: %s" % task.title updateTaskFromVTodo(task, vTodo) else: raise YokadiException("Task %s does exist in yokadi db " % id) else: # This is a new task print "New task %s (%s)" % (vTodo["summary"], vTodo["UID"]) keywordDict = {} task = dbutils.addTask(INBOX_PROJECT, vTodo["summary"], keywordDict, interactive=False) # Keep record of new task origin UID to avoid duplicate # if user update it right after creation without reloading the # yokadi UID # TODO: add purge for old UID self.newTask[vTodo["UID"]] = task.id
def exit(self, status=0, msg=None): if msg: sys.stderr.write(msg) if status == 0: raise YokadiOptionParserNormalExitException() else: raise YokadiException(msg)
def _realProjectName(self, name): if name == '_': if self.lastProjectName is None: raise YokadiException("No previous project used") else: self.lastProjectName = name return self.lastProjectName
def _parseListLine(self, parser, line): """ Parse line with parser, returns a tuple of the form (options, projectList, filters) """ args = parser.parse_args(line) if len(args.filter) > 0: projectName, keywordFilters = parseutils.extractKeywords(u" ".join( args.filter)) else: projectName = "" keywordFilters = [] if self.kFilters: # Add keyword filter keywordFilters.extend(self.kFilters) if not projectName: if self.pFilter: # If a project filter is defined, use it as none was provided projectName = self.pFilter else: # Take all project if none provided projectName = "%" if projectName.startswith("!"): projectName = self._realProjectName(projectName[1:]) projectList = Project.select(NOT(LIKE(Project.q.name, projectName))) else: projectName = self._realProjectName(projectName) projectList = Project.select(LIKE(Project.q.name, projectName)) if projectList.count() == 0: raise YokadiException("Found no project matching '%s'" % projectName) # Check keywords exist parseutils.warnIfKeywordDoesNotExist(keywordFilters) # Filtering and sorting according to parameters filters = [] # Filter on keywords for keywordFilter in keywordFilters: filters.append(keywordFilter.filter()) # Search if args.search: for word in args.search: if word.startswith("@"): tui.warning( "Maybe you want keyword search (without -s option) " "instead of plain text search?") filters.append( OR(LIKE(Task.q.title, "%" + word + "%"), LIKE(Task.q.description, "%" + word + "%"))) return args, projectList, filters
def do_c_set(self, line): """Set a configuration key to value : c_set <key> <value>""" line = line.split() if len(line) < 2: raise BadUsageException("You should provide two arguments : the parameter key and the value") name = line[0] value = " ".join(line[1:]) session = db.getSession() p = session.query(Config).filter_by(name=name, system=False) if p.count() == 0: raise YokadiException("Sorry, no parameter match") else: if self.checkParameterValue(name, value): p[0].value = value tui.info("Parameter updated") else: raise YokadiException("Parameter value is incorrect")
def getTaskFromId(self, tid): if tid == '_': if self.lastTaskId is None: raise YokadiException("No previous task defined") tid = self.lastTaskId task = dbutils.getTaskFromId(tid) if tid != '_': self.lastTaskId = task.id return task
def getTaskFromId(tid, parameterName="id"): """Returns a task given its id, or raise a YokadiException if it does not exist. @param tid: taskId string @param parameterName: name of the parameter to mention in exception @return: Task instance or None if existingTask is False""" # We do not use line.isdigit() because it returns True if line is '¹'! try: taskId = int(tid) except ValueError: raise YokadiException("<%s> should be a number" % parameterName) try: task = Task.get(taskId) except SQLObjectNotFound: raise YokadiException("Task %s does not exist. Use t_list to see all tasks" % taskId) return task
def do_a_edit_name(self, line): """Edit the name of an alias. a_edit_name <alias name>""" session = db.getSession() name = line if not name in self.aliases: raise YokadiException("There is no alias named {}".format(name)) newName = tui.editLine(name) newName = parseutils.parseOneWordName(newName) if newName in self.aliases: raise YokadiException("There is already an alias named {}.".format(newName)) session = db.getSession() db.Alias.rename(session, name, newName) session.commit() self._updateAliasDict()
def getTaskFromId(tid): """Returns a task given its id, or raise a YokadiException if it does not exist. @param tid: taskId string @return: Task instance or None if existingTask is False""" session = db.getSession() # We do not use line.isdigit() because it returns True if line is '¹'! try: taskId = int(tid) except ValueError: raise YokadiException("task id should be a number") try: task = session.query(Task).filter_by(id=taskId).one() except NoResultFound: raise YokadiException( "Task %s does not exist. Use t_list to see all tasks" % taskId) return task
def acquire(self): """Acquire a lock for that task and remove any previous stale lock""" lock = self._getLock() if lock: if lock.updateDate < datetime.now() - 2 * timedelta(seconds=tui.MTIME_POLL_INTERVAL): # Stale lock, removing TaskLock.delete(lock.id) else: raise YokadiException("Task %s is already locked by process %s" % (lock.task.id, lock.pid)) TaskLock(task=self.task, pid=os.getpid(), updateDate=datetime.now())
def parseDateTimeDelta(line): # FIXME: Do we really want to support float deltas? try: delta = float(line[:-1]) except ValueError: raise YokadiException("Timeshift must be a float or an integer") suffix = line[-1].upper() if suffix == "W": return timedelta(days=delta * 7) elif suffix == "D": return timedelta(days=delta) elif suffix == "H": return timedelta(hours=delta) elif suffix == "M": return timedelta(minutes=delta) else: raise YokadiException( "Unable to understand time shift. See help t_set_due")
def getVersion(): if not Config.tableExists(): # There was no Config table in v1 return 1 try: return int(Config.byName(DB_VERSION_KEY).value) except SQLObjectNotFound: raise YokadiException( "Configuration key '%s' does not exist. This should not happen!" % DB_VERSION_KEY)
def do_p_edit(self, line): """Edit a project. p_edit <project name>""" session = db.getSession() project = dbutils.getOrCreateProject(line, createIfNeeded=False) if not project: raise YokadiException("Project does not exist.") # Edit line = tui.editLine(project.name) # Update project projectName = parseutils.parseOneWordName(line) try: project.name = projectName session.commit() except IntegrityError: session.rollback() raise YokadiException("A project named %s already exists. Please find another name" % projectName)
def default(self, line): nline = resolveAlias(line, self.aliases) if nline != line: return self.onecmd(nline) elif nline.isdigit(): self.do_t_show(nline) elif nline == "_": self.do_t_show(nline) else: raise YokadiException( "Unknown command. Use 'help' to see all available commands")
def _parseListLine(self, parser, line): """ Parse line with parser, returns a tuple of the form (options, projectList, filters) """ args = parser.parse_args(line) if len(args.filter) > 0: projectName, filters = parseutils.extractKeywords(" ".join( args.filter)) else: projectName = "" filters = [] if self.kFilters: # Add keyword filter filters.extend(self.kFilters) if not projectName: if self.pFilter: # If a project filter is defined, use it as none was provided projectName = self.pFilter else: # Take all project if none provided projectName = "%" if projectName.startswith("!"): projectName = self._realProjectName(projectName[1:]) projectList = self.session.query(Project).filter( Project.name.notlike(projectName)).all() else: projectName = self._realProjectName(projectName) projectList = self.session.query(Project).filter( Project.name.like(projectName)).all() if len(projectList) == 0: raise YokadiException("Found no project matching '%s'" % projectName) # Check keywords exist parseutils.warnIfKeywordDoesNotExist(filters) # Search if args.search: for word in args.search: if word.startswith("@"): tui.warning( "Maybe you want keyword search (without -s option) " "instead of plain text search?") condition = or_(Task.title.like("%" + word + "%"), Task.description.like("%" + word + "%")) filters.append(DbFilter(condition)) return args, projectList, filters
def parseKeyword(line): """Parse given line to create a keyword filter @return: a KeywordFilter instance""" operators = ("!=", "=") if " " in line: raise YokadiException("Keyword filter should not contain spaces") name = None negative = False value = None valueOperator = None if line.startswith("!"): negative = True line = line[1:] if not line.startswith("@"): raise YokadiException("Keyword name must be prefixed with a @") line = line[1:] # Squash @ line = line.replace("==", "=") # Tolerate == syntax for operator in operators: if operator in line: name, value = line.split(operator, 1) valueOperator = operator try: value = int(value) except ValueError: raise YokadiException( "Value of %s keyword must be an integer (got %s)" % (name, value)) break else: # No operator found, only keyword name has been provided name = line return KeywordFilter(name, negative=negative, value=value, valueOperator=valueOperator)
def do_c_get(self, line): parser = self.parser_c_get() args = parser.parse_args(line) key = args.key if not key: key = "%" k = Config.select( AND(LIKE(Config.q.name, key), Config.q.system == args.system)) fields = [(x.name, "%s (%s)" % (x.value, x.desc)) for x in k] if fields: tui.renderFields(fields) else: raise YokadiException("Configuration key %s does not exist" % line)
def do_c_get(self, line): parser = self.parser_c_get() args = parser.parse_args(line) key = args.key if not key: key = "%" session = db.getSession() k = session.query(Config).filter(Config.name.like(key)).filter_by(system=args.system).all() fields = [(x.name, "%s (%s)" % (x.value, x.desc)) for x in k] if fields: tui.renderFields(fields) else: raise YokadiException("Configuration key %s does not exist" % line)
def getVersion(self): if not self.engine.has_table("config"): # There was no Config table in v1 return 1 try: return int( self.session.query(Config).filter_by( name=DB_VERSION_KEY).one().value) except NoResultFound: raise YokadiException( "Configuration key '%s' does not exist. This should not happen!" % DB_VERSION_KEY)
def getProjectFromName(name, parameterName="project_name"): """ Helper function which returns a project given its name, or raise a YokadiException if it does not exist. """ name = name.strip() if len(name) == 0: raise BadUsageException("Missing <%s> parameter" % parameterName) try: session = db.getSession() return session.query(Project).filter_by(name=name).one() except NoResultFound: raise YokadiException("Project '%s' not found. Use p_list to see all projects." % name)
def getWeekDayNumberFromDay(day): """Return week day number (0-6) from week day name (short or long) @param day: week day as a string in short or long format (in english) @type day: str @return: week day number (int)""" if len(day) == 2 and day in SHORT_WEEKDAYS: dayNumber = SHORT_WEEKDAYS[day] elif day in WEEKDAYS: dayNumber = WEEKDAYS[day] else: raise YokadiException( "Day must be one of the following: [mo]nday, [tu]esday, [we]nesday, [th]ursday, [fr]iday, [sa]turday, [su]nday" ) return dayNumber
def do_t_project(self, line): """Set task's project. t_project <id> <project>""" tokens = parseutils.simplifySpaces(line).split(" ") if len(tokens) != 2: raise YokadiException( "You should give two arguments: <task id> <project>") task = self.getTaskFromId(tokens[0]) projectName = tokens[1] projectName = self._realProjectName(projectName) task.project = dbutils.getOrCreateProject(projectName) if task.project: print "Moved task '%s' to project '%s'" % (task.title, projectName)
def __init__(self, message, lineNumber, line): fullMessage = "Error line %d (\"%s\"): %s" % (lineNumber + 1, line, message) YokadiException.__init__(self, fullMessage) self.lineNumber = lineNumber self.message = message