easydoneit-cli

Easydoneit cli is a simple task manager for terminals.
git clone https://noulin.net/git/easydoneit-cli.git
Log | Files | Refs | README | LICENSE

commit 12f689dce35e14fbcd66c6e51583844edbe04bed
parent cc4cbb8d15e228008cfc2c32b5870abfb22486c1
Author: Remy Noulin <loader2x@gmail.com>
Date:   Mon, 20 Oct 2014 20:56:21 +0200

Version 1.3

Diffstat:
MLICENSE | 0
MREADME.md | 0
AVERSION | 1+
Aedi | 23+++++++++++++++++++++++
Aedi.py | 4174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aedi_common.py | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aedi_core.py | 2673+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aroot.txt | 1+
8 files changed, 6944 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE diff --git a/README.md b/README.md diff --git a/VERSION b/VERSION @@ -0,0 +1 @@ +v1.3 diff --git a/edi b/edi @@ -0,0 +1,23 @@ +#! /bin/bash +# Copyright (C) 2014 Spartatek AB +# +# contact@spartatek.se +# http://spartatek.se +# +# EASYDONEIT CLI is free software: you can redistribute it and/or modify +# it under the terms of the GNU Genereric Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# EASYDONIT CLI is distributed in the hope that it will be usesul, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Genereral Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# GNU licences http://www.gnu.org/licenses +# +# +# call python to avoid permission denied +python `dirname $0`/edi.py "$@" diff --git a/edi.py b/edi.py @@ -0,0 +1,4174 @@ +#! /usr/bin/env python +# -*- coding: latin-1 -*- +## @package edi +# Documentation for edi module. All Easydoneit CLI commands are defined in this module. +# +# Copyright (C) 2014 Spartatek AB +# +# contact@spartatek.se +# http://spartatek.se +# +# EASYDONEIT CLI is free software: you can redistribute it and/or modify +# it under the terms of the GNU Genereric Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# EASYDONIT CLI is distributed in the hope that it will be usesul, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Genereral Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# GNU licences http://www.gnu.org/licenses + +import sys +import re +import shutil +import os +import ConfigParser +# 0 disable, 1 enable print +#:print_command_map=0 +#:profiler=0 + +#:profiler +#:end + +from edi_core import * +# Import to access data_location in edi_core +import edi_core + +## functions to print results from list_tree/list_group +# @ingroup EDI_CLI +def empty_line(tid,s): + return '' + +## functions to print results from list_tree/list_group +# @ingroup EDI_CLI +def print_list(task_array): + Functions = {'head group': generate_group_string_with_tid,'element': generate_task_string_with_tid} + for t in task_array: + print Functions.get(t['head'],empty_line)(t['tid'], '%3d - %s %s'%(t['position'],t['group'],t['title'])) + +#-------------------------------------------- +# COMMAND LINE INTERFACE + +## Default Function. +# @ingroup EDI_CLI +def Nothing(): + print 'Command not available' + +## add command name to COMMANDS, help to HELP_STRING and corresponding python functions to FUNCTIONS below +COMMANDS = ['help','version','cr','lsid','add','cat','mkdir','show','rm','vi','order','set','reset','search','mv','cp','ln','path','tree','ls','stfi','data','html','select','default','cpb','mvb','fc','bc','createDatabase','topbot','in','re','st','zip','autolink','cv','user','email','stats','many','demo','list','merge','media','at'] + +## ls help for both edi lsid and edi ls +LS_HELP ='''edi lsid [-R|-G|-s|-L|-La|-Lx|-t] [group|task] [position] + list tasks in a group. Outside tree folder, without parameter list all tasks and groups, + with parameter list group or task title (selected with task id or group id and position), + with -R list group recursively, with position print task title. + In tree folder, without parameter list group, + with -G and when cwd is in tree, list group path with ids and titles (group1/group2...), + with task id parameter print tree path and title, + -s for sorted list by status, + -L list tasks in any group from groups in edi list (or), + -La list tasks in all groups in edi list (and), + -Lx list tasks in all groups in edi list exclusively (strictly and) + -t sort by date from oldest to newest task''' + +## help command displays these strings +HELP_STRINGS = ['''edi help [command] + print cli commands and data location, command parameter is optional''', + 'print current version', + '''edi cr [group] + create task and open text editor, calls without parameters create task in root group, + when in tree and without parameter, create task in current group, + when group parameter is provided create task in group''', + LS_HELP, + '''edi add [-F|-t] [group] text_filename|text + add task from file. Without parameter add task in root group, + when in tree and without parameter, add task in current group, + add_task option (-F) text_filename adds task with filename in fist line of description in root group, + add_task option (-F) group text_filename adds task with filename in first line of description in group, + add_task group text_filename adds task in group + edi add -t [group] 'text' creates a task with description text''', + '''edi cat group|task|position [position] + print description. With position parameter print description for task at given position, + when in tree and without group parameter, print description at given position in current group''', + '''edi mkdir task|position + convert task to group + when in tree, convert task at given position''', + '''edi show task|position + print group title for task''', + '''edi rm task|group|position [task] + delete task or group + with group and task parameters, delete task and keep linked tasks + edi rm root deletes all tasks in default database''', + '''edi vi task|group|position [position] + edit description, + in tree, edit task or group at given position in current group''', + '''edi order [group] at_position to_position + change order in group. Without group parameter change order in root group, + in tree change order in current group''', + '''edi set task|group|position int_status + set status to int parameter, + int_status values are: +%s'''%'\n'.join([' %d: %s'%(i,j.strip()) for i,j in enumerate(TASK_STATUS)]), + '''edi reset group + set tasks in group to active. Without parameter set tasks in root group''', + '''edi search string + search string in tasks folder, + in tree, searches in current group''', + '''edi mv group task|group group + edi mv position group + move task or group, + move task or group from group parameter 1 to group parameter 3''', + '''edi cp [-E] task|group|position group|path + copy task to a group, + option -E copy description to path, first line in description is filename''', + '''edi ln task|position group + add a reference in group''', + '''edi path task|group|position + print task filesystem path''', + '''edi tree + print tree path''', + LS_HELP.replace('edi lsid ','edi ls ').replace('list tasks in a group','ls groups and tasks with positions only'), + '''edi stfi status_filter state + enable/disable (set) status filter, + without parameters, print current filters, + + status_filter values are: +%s + + state values are: +%s'''%('\n'.join([' %s'%i.strip() for i in TASK_STATUS]), '\n'.join([' %s'%i for i in STATUS_FILTER_STATES])), + '''edi data [-d] [database_name] [path] + set path for database and create database when it doesnt exist, + without option, print current data path + Option -d delete a database''', + '''edi html [-R|-G|-s|-L|-La|-Lx|-t] group|task [position] + generate html for tasks, same options as ls''', + '''edi select [database_name_1,database_name2...] + set selected databases + without option, print selected databases''', + '''edi default [database_name] + set default database for new tasks + without option, print default database''', + '''edi cpb task|group|position database group + copy task or group to database/group''', + '''edi mvb group task|group database group + edi mvb position database group + move task or group to database/group''', + '''edi fc task|group|position [color] + set/get forground color from task or group + color is 4 decimal numbers seperated with commas + 0,0,0,255''', + '''edi bc task|group|position [color] + set/get background color from task or group + color is 4 decimal numbers seperated with commas + 0,0,0,255''', + '''edi createDatabase database_name path + create database folders and set path + this command creates the path when it doesnt exist''', + '''edi topbot [top|bottom] + select add new tasks in bottom of group or on top of group + Default is bottom + without parameter, print status''', + '''edi in [-A] group|position + print group tasks in md format + when in tree, print group at given position + option -A create an agenda after title for slideshows''', + '''edi re [-A] group|position + print group tasks in reStructuredText format + when in tree, print group at given position + option -A create an agenda after title for slideshows''', + '''edi st + shows trees for selected databases, print all trees: group tids and titles''', + '''edi zip database_name [path] + creates an archive of the database in database_name.tar.bz2 in path or current folder''', + '''edi autolink [group1,group2...] + set configuration for linking new tasks to autolink groups + without option, print autolink group list''', + '''edi cv group|pos + convert empty group to task''', + '''edi user [First name Last name] + set user name + without parameter, display username''', + '''edi email [user email] + set user email + without parameter, display user email''', + '''edi stats [group] + show statistics + without parameter, show statistics in selected databases or current group in tree''', + '''edi many [-1|-g] [group] text_file + add many tasks from a file, tasks are seperated with '---' lines + with -1, add one task per line from a file, + with -g, create a group per line from file, one space for deeper levels''', + '''edi demo + display commands to show how to use Easydoneit CLI''', + '''edi list group1,group2... + set a list of groups for edi ls -L,-La and -Lx options + the groups have to be in the same database + without parameter, list groups''', + '''edi merge [path|database_name] database_name_2 + copy tasks from database_name to database_name_2 with no changes + WARNING - the source database is merged correctly several times when the tree remains the same since the first merge.''', + '''edi media [task|pos] [image file|sound file] + copy media file to task. Only one media file per task is allowed, for more attachments use edi at + without parameter, show media type''', + '''edi at [task|pos] [filename|*] + copy file to task as attachments. + without parameter, show attachment files'''] + +HELP = zip(COMMANDS,HELP_STRINGS) +HELP_DICT = dict(HELP) + +## print cli commands and data location +# +# print format:<br> +# command<br> +# TAB help string +# @ingroup EDI_CLI +def help(): + print 'Easydoneit help' + print 'http://spartatek.se/easydoneit_cli' + print +#:define edi_version + f = open('%s/VERSION'%os.path.abspath(os.path.dirname(sys.argv[0]))) + v = f.readline() + f.close() + print 'Version %s'%v +#:end + print 'Selected data_location' + print edi_core.data_location + print + if len(sys.argv) < 3: + # print complete help + for c,h in HELP: + print c + print ' %s\n'%h + + # print help for specified command + if len(sys.argv) == 3: + if not sys.argv[2] in HELP_DICT.keys(): + sys.exit('%s is invalid command.'%sys.argv[2]) + print sys.argv[2] + print ' %s\n'%HELP_DICT[sys.argv[2]] + + +## print current version +# @ingroup EDI_CLI +def version(): +#:edi_version + f = open('%s/VERSION'%os.path.abspath(os.path.dirname(sys.argv[0]))) + v = f.readline() + f.close() + print 'Version %s'%v + +## create task and open text editor, calls without parameters create task in root group, when group parameter is provided create task in group +# @ingroup EDI_CLI +def create_task_cli(): + # Select group and database + +#:define figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break +#:end + + # Initialize group in case len(sys.argv) != 2 + group = '' + if len(sys.argv) == 2: + # no parameter - just command + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + else: + # default group + group = 'root' + + if (len(sys.argv) == 2) and (group == 'root') and (not status): + # set default database when group is root and cwd not in tree +#:define set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) +#:end + if len(sys.argv) >= 3: + group = sys.argv[2] + + if (group != 'root') and ((not status) or (len(sys.argv) == 3)): + # find group in selected databases and setup data_location when not in tree or group is parameter +#:define find_group_in_selected_databases_and_setup_data_location + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + groups = os.listdir(edi_core.data_location_groups) + if group in groups: + break +#:end + # Verify parameter + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) + print create_task(group) + +## list tasks in a group. Outside tree folder, without parameter list all tasks and groups, with parameter list group or task title, with -R list group recursively, with position print task title. In tree folder, without parameter list group, with -G list group path with ids and titles, with task id parameter print tree path and title +# @ingroup EDI_CLI +def ls_generic(): + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + # Decide what to execute + if len(sys.argv) == 2: + if status: + exe = 'ls in tree' + else: + exe = 'ls root' + else: + if (sys.argv[2] == '-R') or (sys.argv[2] == '-G') or (sys.argv[2] == '-s') or (sys.argv[2] == '-t'): + if len(sys.argv) == 4: + # there is a task id parameter, use that instead of tree location + tid = sys.argv[3] + status = '' + else: + tid = 'root' + else: + tid = sys.argv[2] + + if status: + # in tree, set tid to cwd + tid = cwd.split('/')[-1] + +#:define find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break +#:end + if is_this_task_a_group(sys.argv[2]): + if len(sys.argv) == 3: + exe = 'ls group' + else: + exe = 'ls at position' + elif sys.argv[2] == '-R': + if (is_this_task_a_group(tid)) or (tid == 'tree'): + exe = 'ls tree' + # tid is tree when cwd is at root, change to root for ls -R. + if tid == 'tree': + tid = 'root' + else: + exe = 'ls task' + # replace -R option with tid and run regular ls task + sys.argv[2] = tid + elif sys.argv[2] == '-s': + exe = 'ls sort' + # tid is tree when cwd is at root, change to root for ls -s. + if tid == 'tree': + tid = 'root' + elif sys.argv[2] == '-t': + exe = 'ls sort by date' + # tid is tree when cwd is at root, change to root for ls -t. + if tid == 'tree': + tid = 'root' + elif sys.argv[2] == '-L': + exe = 'ls any selected groups' + elif sys.argv[2] == '-La': + exe = 'ls all in selected groups' + elif sys.argv[2] == '-Lx': + exe = 'ls all in selected groups exclusively' + elif sys.argv[2] == '-G' and status: + exe = 'ls groups in tree path' + else: + exe = 'ls task' + if sys.argv[2] == '-G': + if len(sys.argv) < 4: + exe = 'ls root' + else: + # replace -G option with tid and run regular ls task + sys.argv[2] = sys.argv[3] + + if exe == 'ls in tree': + if cwd.split('/')[-1] == 'tree': + exe = 'ls group root' + else: + print_list(list_group(cwd.split('/')[-1])) + if exe == 'ls root': + # print root in all selected databases +#:define loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + print '%s - %s' % (d,path) + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) +#:end + print_list(list_tree('root')) + if exe == 'ls group root': + print_list(list_group('root')) + if exe == 'ls group': + group = sys.argv[2] + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_group_in_selected_databases_and_setup_data_location + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + groups = os.listdir(edi_core.data_location_groups) + if group in groups: + break + if group == 'root': + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + print '%s - %s' % (d,path) + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + print_list(list_group(group)) + else: + print_list(list_group(group)) + if exe == 'ls at position': + # Find task at given postion + group = sys.argv[2] + try: + position = int(sys.argv[3]) + except: + sys.exit('%s is invalid position'%sys.argv[3]) +#:define find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) +#:end + # End - Find task at given postion + print generate_task_string_with_tid(tid,get_task_title(tid)) + if exe == 'ls tree': + print_list(list_tree(tid)) + if exe == 'ls groups in tree path': + # p is data_location folder/tree + p = '/'.join(p_l) + # if empty then command is run in tree root + if cwd.split(p)[-1]: + # print path of tids: tid/tid... + print cwd.split(p)[-1][1:] + group_titles_in_path = [] + for g in cwd.split(p)[-1][1:].split('/'): + group_titles_in_path.append(get_task_title(g)) + # print title/title... + print '/'.join(group_titles_in_path) + if exe == 'ls task': + if status: + # print group path titles and task + # in tree folder + p = '/'.join(p_l) + # if empty then command is run in tree root + if cwd.split(p)[-1]: + print cwd.split(p)[-1][1:] + group_titles_in_path = [] + for g in cwd.split(p)[-1][1:].split('/'): + group_titles_in_path.append(get_task_title(g)) + # print title/title... + print '/'.join(group_titles_in_path) + # Verify that task sys.argv[2] is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + print generate_task_string_with_tid(sys.argv[2],get_task_title(sys.argv[2])) + else: + #find_task_in_selected_databases_and_setup_data_location + tid_in_database = '' + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + tid_in_database = 'yes' + break + if not tid_in_database: + sys.exit('%s not found.'%sys.argv[2]) + print generate_task_string_with_tid(sys.argv[2],get_task_title(sys.argv[2])) + + if exe == 'ls sort': + if not is_this_task_a_group(tid): + sys.exit('%s is not a group.'%tid) + task_attributes = list_group(tid) + sorted_tasks = sort_task_attributes(task_attributes) + print_list(sorted_tasks) + + if exe == 'ls sort by date': + if not is_this_task_a_group(tid): + sys.exit('%s is not a group.'%tid) + task_attributes = list_group(tid) + sorted_tasks = sort_task_attributes_by_date(task_attributes) + print_list(sorted_tasks) + + if exe == 'ls any selected groups': + if not edi_core.list_of_groups: + sys.exit('List empty') + + tid = edi_core.list_of_groups[0] + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + + # check that all groups are available in the database + status = 'search groups' + for g in edi_core.list_of_groups: + if not is_this_task_a_group(g): + status = 'group not found, edi cannot list all groups' + if status == 'group not found, edi cannot list all groups': + sys.exit(status) + + # list tasks in all groups + task_attributes = list_group(tid) + empty_line = task_attributes[-1] + # remove empty lines between groups + del task_attributes[-1] + + # add other groups to the list + for tid in edi_core.list_of_groups[1:]: + # remove head group, keep head group only for first group in the group list + # remove empty lines between groups + task_attributes += list_group(tid)[1:-1] + + # remove duplicated tasks + tasks = [] + tasks.append(task_attributes[0]) + # array used to prevent duplicated tasks + tids = [] + for t in task_attributes[1:]: + if not t['tid'] in tids: + tasks.append(t) + tids.append(t['tid']) + + tasks.append(empty_line) + print_list(tasks) + + + if exe == 'ls all in selected groups': + if not edi_core.list_of_groups: + sys.exit('List empty') + + tid = edi_core.list_of_groups[0] + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + + if len(edi_core.list_of_groups) == 1: + if not is_this_task_a_group(tid): + sys.exit('%s not found, edi cannot list all groups' % tid) + # only one group, regular ls + print_list(list_group(tid)) + sys.exit(0) + + # check that all groups are available in the database + status = 'search groups' + for g in edi_core.list_of_groups: + if not is_this_task_a_group(g): + status = 'group not found, edi cannot list all groups' + if status == 'group not found, edi cannot list all groups': + sys.exit(status) + + # list tasks in all groups + task_attributes = list_group(tid) + empty_line = task_attributes[-1] + # remove empty lines between groups + del task_attributes[-1] + + # add other groups to the list + for tid in edi_core.list_of_groups[1:]: + # remove head group, keep head group only for first group in the group list + # remove empty lines between groups + task_attributes += list_group(tid)[1:-1] + + # keep tasks only in all listed groups + tasks = [] + tasks.append(task_attributes[0]) + # array used to prevent duplicated tasks + tids = [] + for t in task_attributes[1:]: + if is_linked(t['tid']): + groups = os.listdir('%s/groups/' % generate_task_path(t['tid'])) + # check that the task in all listed group + status = 'all in groups' + for g in edi_core.list_of_groups: + if not g in groups: + status = 'missing group' + if (status == 'all in groups') and (not t['tid'] in tids): + tasks.append(t) + tids.append(t['tid']) + + + tasks.append(empty_line) + print_list(tasks) + + if exe == 'ls all in selected groups exclusively': + if not edi_core.list_of_groups: + sys.exit('List empty') + + tid = edi_core.list_of_groups[0] + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + + # check that all groups are available in the database + status = 'search groups' + for g in edi_core.list_of_groups: + if not is_this_task_a_group(g): + status = 'group not found, edi cannot list all groups' + if status == 'group not found, edi cannot list all groups': + sys.exit(status) + + # list tasks in all groups + task_attributes = list_group(tid) + empty_line = task_attributes[-1] + # remove empty lines between groups + del task_attributes[-1] + + # add other groups to the list + for tid in edi_core.list_of_groups[1:]: + # remove head group, keep head group only for first group in the group list + # remove empty lines between groups + task_attributes += list_group(tid)[1:-1] + + # keep tasks only in all listed groups + tasks = [] + tasks.append(task_attributes[0]) + # array used to prevent duplicated tasks + tids = [] + for t in task_attributes[1:]: + if is_linked(t['tid']): + groups = os.listdir('%s/groups/' % generate_task_path(t['tid'])) + # check that the task in all listed group exclusively + status = 'all groups in list_of_groups' + for g in groups: + if not g in edi_core.list_of_groups: + status = 'a group is not in the list' + if (status == 'all groups in list_of_groups') and (len(groups) == len(edi_core.list_of_groups)) and (not t['tid'] in tids): + tasks.append(t) + tids.append(t['tid']) + + + tasks.append(empty_line) + print_list(tasks) + + +## list with tids +# @ingroup EDI_CLI +def lsid(): + # list tids and positions + edi_core.list_option = 'tids' + + ls_generic() + +# ls groups and tasks with positions +# @ingroup EDI_CLI +def ls(): + # list positions only + edi_core.list_option = 'positions' + + ls_generic() + +## add task from file. Without parameter add task in root group. add_task option (-F) text_filename add task with filename in fist line of description in root group. add_task option (-F) group text_filename add task with filename in first line of description in group. add_task group text_filename add task in group +# @ingroup EDI_CLI +def add_task_cli(): + if len(sys.argv) < 3: + # not enough parameters, call create_task + create_task_cli() + return + +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + options = '' + group_parameter_status = 'group is a command line parameter' + if len(sys.argv) < 4: + # Parameters are: command filename + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + else: + # default group + group = 'root' + group_parameter_status = 'edi selected group' + + text_file = sys.argv[2] + else: + if ((sys.argv[2] == '-F') or (sys.argv[2] == '-t')) and len(sys.argv) == 4: + options = sys.argv[2] + text_file = sys.argv[3] + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + else: + # default group + group = 'root' + group_parameter_status = 'edi selected group' + if ((sys.argv[2] == '-F') or (sys.argv[2] == '-t')) and len(sys.argv) == 5: + options = sys.argv[2] + group = sys.argv[3] + text_file = sys.argv[4] + if ((sys.argv[2] != '-F') and (sys.argv[2] != '-t')): + group = sys.argv[2] + text_file = sys.argv[3] + if not options: + exe = 'add' + else: + if options == '-F': + # option -F stores text_file filename in first line of description + exe = 'add task and filename' + elif options == '-t': + # option -F stores text_file filename in first line of description + exe = 'add text from command line' + + if (group == 'root') and (not status): + # set default database when group is root and cwd not in tree +#:set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + if (group != 'root') and ((not status) or (group_parameter_status == 'group is a command line parameter')): +#:find_group_in_selected_databases_and_setup_data_location + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + groups = os.listdir(edi_core.data_location_groups) + if group in groups: + break + + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) + + if exe == 'add': + if not os.path.exists(text_file): + sys.exit('%s not accessible.'%text_file) + print add_task(group,text_file) + if exe == 'add task and filename': + if not os.path.exists(text_file): + sys.exit('%s not accessible.'%text_file) + print add_task_and_filename(group,text_file) + if exe == 'add text from command line': + print add_text(group,text_file) + +## print description. With position parameter print description for task at given position +# @ingroup EDI_CLI +def cat(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + tid = sys.argv[2] + + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:define find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break +#:end + + if status == 'in tree': +#:define get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:end + + if (len(sys.argv) == 3) and (not status): + # group parameter and not in tree + tid = sys.argv[2] + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + + if (len(sys.argv) == 3) and (status == 'in tree'): + # Find task at given postion +#:define get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:end +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + + if len(sys.argv) == 4: + group = sys.argv[2] + try: + position = int(sys.argv[3]) + except: + sys.exit('%s is invalid position'%sys.argv[3]) + # find_task_in_selected_databases_and_setup_data_location needs tid, here tid = group + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not tid in tasks: + sys.exit('%s not found.'%tid) + print ''.join(display_task(tid)) + +## convert task to group +# @ingroup EDI_CLI +def mkdir(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:define tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break +#:end + + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + # Verify that task is not already a group + if is_this_task_a_group(sys.argv[2]): + sys.exit('%s is already a group.'%sys.argv[2]) + print '\n'.join(create_group(sys.argv[2])) + +## print group title for task +# @ingroup EDI_CLI +def show_group_for_task_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + print '\n'.join(show_group_for_task(sys.argv[2])) + +## delete task or group, or delete task in specified group, keep linked tasks +# @ingroup EDI_CLI +def rm(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + if len(sys.argv) == 3: + if is_this_task_a_group(sys.argv[2]): + delete_group(sys.argv[2]) + else: + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + delete_task(sys.argv[2]) + if len(sys.argv) == 4: + # Verify that task tid is not root + if sys.argv[3] == 'root': + sys.exit('root is invalid task') + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + # Verify group exists + if not is_this_task_a_group(sys.argv[2]): + sys.exit('%s is not a group.'%sys.argv[2]) + if not sys.argv[3] in tasks: + sys.exit('%s not found.'%sys.argv[3]) + # delete task in specified group + delete_linked_task(sys.argv[2], sys.argv[3]) + +## edit description +# @ingroup EDI_CLI +def vi(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + tid = sys.argv[2] + + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + + if (len(sys.argv) == 3) and (not status): + # group parameter and not in tree + tid = sys.argv[2] + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + if (len(sys.argv) == 3) and (status == 'in tree'): + # Find task at given postion +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + if len(sys.argv) == 4: + group = sys.argv[2] + try: + position = int(sys.argv[3]) + except: + sys.exit('%s is invalid position'%sys.argv[3]) + # find_task_in_selected_databases_and_setup_data_location needs tid, here tid = group + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + # Verify group exists + if not is_this_task_a_group(sys.argv[2]): + sys.exit('%s is not a group.'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not tid in tasks: + sys.exit('%s not found.'%tid) + edit_task(tid) + +## change order in group. Without group parameter change order in root group, in tree change order in current group +# @ingroup EDI_CLI +def change_order(): + if len(sys.argv) < 4: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + else: + group = 'root' + + + if len(sys.argv) < 5: + if not status: + sys.exit('Missing group parameter.') + at = sys.argv[2] + to = sys.argv[3] + else: + group = sys.argv[2] + at = sys.argv[3] + to = sys.argv[4] + + if group == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + #find_group_in_selected_databases_and_setup_data_location + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + groups = os.listdir(edi_core.data_location_groups) + if group in groups: + break + + # Verify group exists + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) + # Verify at and to are numbers + try: + test = int(at) + except: + sys.exit('%s is invalid position'%at) + try: + test = int(to) + except: + sys.exit('%s is invalid position'%to) + + if int(at) < 0: + sys.exit('Position %s is invalid.'%at) + if int(to) < 0: + sys.exit('Position %s is invalid.'%to) + print '\n'.join(change_task_order(group,int(at),int(to))) + +## set status to int parameter +# @ingroup EDI_CLI +def set(): + if len(sys.argv) < 4: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + # Verify int_status parameter + try: + test = int(sys.argv[3]) + except: + sys.exit('int_status %s is invalid.'%sys.argv[3]) + if test >= len(edi_core.TASK_STATUS): + sys.exit('int_status %s is too large.'%sys.argv[3]) + if test < 0: + sys.exit('int_status %s is invalid.'%sys.argv[3]) + set_status(sys.argv[2],int(sys.argv[3])) + +## set tasks to active. Without parameter set tasks in root group +# @ingroup EDI_CLI +def reset(): + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + + if (len(sys.argv) == 2) and (status != 'in tree'): +#:set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + group = 'root' + if (len(sys.argv) > 2) and (sys.argv[2] == 'root') and (not status): + # set default database when group is root and cwd not in tree +#:set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + group = 'root' + # when group is root and in tree, dont change data_location + if (len(sys.argv) > 2) and (sys.argv[2] != 'root'): + group = sys.argv[2] +#:find_group_in_selected_databases_and_setup_data_location + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + groups = os.listdir(edi_core.data_location_groups) + if group in groups: + break + + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) + reset_group_status(group) + +## search string in tasks folder +# @ingroup EDI_CLI +def search(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + print ''.join(search_string_in_tree(group,sys.argv[2])) + else: +#:loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + print '%s - %s' % (d,path) + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + print ''.join(search_string(sys.argv[2])) + +## move task or group +# @ingroup EDI_CLI +def move_task(): + if len(sys.argv) < 4: + sys.exit('Too little parameters.') + if len(sys.argv) == 5: + tid = sys.argv[2] +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + # Verify parameters + # Verify that task tid is not root + if sys.argv[3] == 'root': + sys.exit('root is invalid task') + if not is_this_task_a_group(sys.argv[2]): + sys.exit('%s is not a group.'%sys.argv[2]) + if not is_this_task_a_group(sys.argv[4]): + sys.exit('%s is not a group.'%sys.argv[4]) + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[3] in tasks: + sys.exit('%s not found.'%sys.argv[3]) + # Verify that when tid is a group, tid is not parent to destination group (sys.argv[3]) + if is_this_task_a_group(sys.argv[3]): + tree_path = find_group_in_tree(sys.argv[4]) + parent_path = tree_path.split(sys.argv[4])[0] + if sys.argv[3] in parent_path: + print tree_path + sys.exit('%s is a parent group for %s'%(sys.argv[3],sys.argv[4])) + # Verify that group is not move on itself + if sys.argv[3] == sys.argv[4]: + sys.exit('Moving %s group on itself.'%sys.argv[3]) + # Verify that source group is not destination group + if sys.argv[2] == sys.argv[4]: + sys.exit('Source and destination groups are identical %s.'%sys.argv[2]) + tid = move_task_to_a_group(sys.argv[2],sys.argv[3],sys.argv[4]) + if len(tid) > edi_core.ID_LENGTH: + # Error when the task sys.argv[3] is not found in sys.argv[2] + sys.exit(tid) + return + + # Always 4 parameters - other parameters are ignored + # Parameter 2 is position, find group + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + + # Verify parameters + if not 'group' in locals(): + sys.exit('Invalid parameters.') + if not is_this_task_a_group(sys.argv[3]): + sys.exit('%s is not a group.'%sys.argv[3]) + # Verify that when tid is a group, tid is not parent to destination group (sys.argv[3]) + if is_this_task_a_group(tid): + tree_path = find_group_in_tree(sys.argv[3]) + parent_path = tree_path.split(sys.argv[3])[0] + if tid in parent_path: + print tree_path + sys.exit('%s is a parent group for %s'%(tid,sys.argv[3])) + # Verify that group is not move on itself + if tid == sys.argv[3]: + sys.exit('Moving %s group on itself.'%tid) + # Verify that source group is not destination group + if group == sys.argv[3]: + sys.exit('Source and destination groups are identical %s.'%group) + move_task_to_a_group(group,tid,sys.argv[3]) + +## copy task to a group. option -E copy description to path, first line in description is filename +# @ingroup EDI_CLI +def copy_task(): + # Verify parameters + if len(sys.argv) < 4: + sys.exit('Too little parameters.') + # option + if sys.argv[2] == '-E': + # Verify parameters + if len(sys.argv) < 5: + sys.exit('Too little parameters.') + option = '-E' + sys.argv[2] = sys.argv[3] + tid = sys.argv[3] + else: + option = '' + tid = sys.argv[2] + + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + + if option == '-E': + # Export task to a file + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + if not os.path.exists(sys.argv[4]): + sys.exit('%s is unreachable.'%sys.argv[4]) + + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not tid in tasks: + sys.exit('%s not found.'%tid) + + r = export_task_to_a_file(tid,sys.argv[4]) + print 'Exported task to %s'%r + else: + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + # Verify that task tid is not root + if tid == 'root': + sys.exit('root is invalid task') + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not tid in tasks: + sys.exit('%s not found.'%tid) + + if not is_this_task_a_group(sys.argv[3]): + sys.exit('%s is not a group.'%sys.argv[3]) + if tid == sys.argv[3]: + sys.exit('Source and destination are identical.') + # Verify that when tid is a group, tid is not parent to destination group (sys.argv[3]) + if is_this_task_a_group(tid): + tree_path = find_group_in_tree(sys.argv[3]) + parent_path = tree_path.split(sys.argv[3])[0] + if tid in parent_path: + print tree_path + sys.exit('%s is a parent group for %s'%(tid,sys.argv[3])) + print copy_task_to_a_group(tid,sys.argv[3]) + +## add a reference in group +# @ingroup EDI_CLI +def link_task(): + if len(sys.argv) < 4: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + # Verify parameters + if not is_this_task_a_group(sys.argv[3]): + sys.exit('%s is not a group.'%sys.argv[3]) + if sys.argv[3] == find_group_containing_task(sys.argv[2]): + sys.exit('Task %s is already in group %s.'%(sys.argv[2],sys.argv[3])) + add_task_reference_to_a_group(sys.argv[2],sys.argv[3]) + +## print task filesystem path +# @ingroup EDI_CLI +def get_task_path(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + print generate_task_path(sys.argv[2]) + +## print tree path +# @ingroup EDI_CLI +def tree(): + print edi_core.data_location_tree + + DATABASE_NAME = 0 + DATABASE_PATH = 1 + print + print 'trees' + for d in edi_core.databases: + print '%s - %s/tree' % (d[DATABASE_NAME], d[DATABASE_PATH]) + +## enable/disable (set) status filter +# @ingroup EDI_CLI +def set_status_filter(): + if len(sys.argv)<3: + print edi_core.status_filters_d + else: + if len(sys.argv)<4: + sys.exit('Too little parameters.') + status_name = sys.argv[2] + state = sys.argv[3] + + # verify parameters + task_status = [i.strip().lower() for i in edi_core.TASK_STATUS] + if not status_name.lower() in task_status: + sys.exit('%s is invalid status_filter. Possible status filter are %s.'%(status_name,','.join(task_status))) + if not state in edi_core.STATUS_FILTER_STATES: + sys.exit('%s is invalid state. Possible states are %s.'%(state,','.join(edi_core.STATUS_FILTER_STATES))) + +#:define load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() +#:end + config.set('filters',status_name,state) + +#:define save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() +#:end + +## set path for database +# @ingroup EDI_CLI +def data(): + if len(sys.argv)<3: + print 'Selected data_location' + print edi_core.data_location + DATABASE_NAME = 0 + DATABASE_PATH = 1 + print + print 'Available databases' + for d in edi_core.databases: + print '%s - %s' % (d[DATABASE_NAME], d[DATABASE_PATH]) + else: + if len(sys.argv)<4: + sys.exit('Too little parameters.') +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + if sys.argv[2] == '-d': + name = sys.argv[3] + try: + path = config.get('data',name) + except: + sys.exit('%s is invalid database name.'%name) + if name == 'location': + sys.exit('%s is invalid database name.'%name) + + # remove database from selected configuration and check that at least one database is selected + selected = config.get('locations','selected') + selected_l = selected.split(',') + new_selected_l = [] + for i in selected_l: + if not name in i: + new_selected_l.append(i) + if not new_selected_l: + sys.exit('Select more databases before deleting %s'%name) + new_selected = ','.join(new_selected_l) + config.set('locations','selected',new_selected) + + # delete database folder from file system + # delete database from config file + + # delete database folder from file system + if os.path.exists(path): + shutil.rmtree(path) + # delete database from config file - data, selected and default_add_in + config.remove_option('data',name) + + default = config.get('locations','default_add_in') + if name == default: + default = new_selected_l[0] + config.set('locations','default_add_in',default) + + #save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + #end + + print 'Deleted database %s - %s' % (name,path) + print '\nSelected databases' + print new_selected + print '\nDefault for new tasks' + print default + return + + name = sys.argv[2] + if name == 'location': + sys.exit('%s is invalid database name.'%name) + path = os.path.expanduser(sys.argv[3]) + + config.set('data',name,path) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + edi_core.data_location = path + init() + +## generate html for tasks +# @ingroup EDI_CLI +def html_cli(): + # generate html header and content + edi_core.list_option = 'html' + + html_header = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <title>Tasks</title> + <style type="text/css"> + .subject {text-align: left} + .plannedStartDateTime {text-align: right} + .priority {text-align: right} + .inactive {color: #5E5E5E} + .late {color: #A020F0} + .active {color: #000000} + .duesoon {color: #FF8000} + .overdue {color: #FF0000} + .completed {color: #037700} + + body { + color: #333; + background-color: white; + font: 11px verdana, arial, helvetica, sans-serif; + } + + /* Styles for the title and table caption */ + h1, caption { + text-align: center; + font-size: 18px; + font-weight: 900; + color: #778; + } + + /* Styles for the whole table */ + #table { + border-collapse: collapse; + border: 2px solid #ebedff; + margin: 10px; + padding: 0; + } + + /* Styles for the header row */ + .header { + font: bold 12px/14px verdana, arial, helvetica, sans-serif; + color: #07a; + background-color: #ebedff; + } + + /* Mark the column that is sorted on */ + #sorted { + text-decoration: underline; + } + + /* Styles for a specific column */ + .subject { + font-weight: bold; + } + + /* Styles for regular table cells */ + td { + padding: 5px; + border: 2px solid #ebedff; + } + + /* Styles for table header cells */ + th { + padding: 5px; + border: 2px solid #ebedff; + } + + </style> + </head> + <body> + <h1>Tasks</h1> + <table border="1" id="table"> + <thead> + <tr class="header"> + <th class="subject" scope="col">Subject</th> + <th class="description" scope="col">Description</th> + </tr> + </thead> + <tbody>''' + + html_footer = ''' </tbody> + </table> + </body> +</html>''' + + print html_header + ls_generic() + print html_footer + +## list selected databases or set selected databases +# @ingroup EDI_CLI +def selected_cli(): + if len(sys.argv)>3: + sys.exit('Too many parameters.') + if len(sys.argv)<3: + print 'Selected databases' + print ','.join(edi_core.selected) + print + z = zip(edi_core.selected, edi_core.selected_path) + for d,p in z: + print '%s - %s' % (d,p) + if len(sys.argv)==3: +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + # Verify parameter + selected = sys.argv[2].split(',') + if 'location' in selected: + sys.exit('location is invalid database name.') + for d in selected: + if not d in dict(edi_core.databases).keys(): + sys.exit('%s is invalid database name.'%d) + if not edi_core.default_add_in in selected: + # default becomes first selected database + print 'Default database %s is not selected'%edi_core.default_add_in + print '%s is now default'%sys.argv[2].split(',')[0] + config.set('locations','default_add_in',sys.argv[2].split(',')[0]) + config.set('locations','selected',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + +## set default database +# @ingroup EDI_CLI +def default_cli(): + if len(sys.argv)>3: + sys.exit('Too many parameters.') + if len(sys.argv)<3: + print 'Default for new tasks' + print edi_core.default_add_in + z = dict(zip(edi_core.selected, edi_core.selected_path)) + print z[edi_core.default_add_in] + if len(sys.argv)==3: + # Verify parameter + if not sys.argv[2] in edi_core.selected: + sys.exit('%s is not selected'%sys.argv[2]) +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('locations','default_add_in',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + + +## copy task or group to database/group +# @ingroup EDI_CLI +def cpb_cli(): + if len(sys.argv) < 5: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify parameters + if not sys.argv[3] in edi_core.selected: + sys.exit('%s database is not selected.'%sys.argv[3]) + # Verify that task tid is not root + if sys.argv[2] == 'root': + sys.exit('root is invalid task') + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + z = dict(zip(edi_core.selected, edi_core.selected_path)) + data_location = os.path.expanduser(z[sys.argv[3]]) + data_location_groups = '%s/groups'%data_location + if not os.path.exists('%s/%s'%(data_location_groups,sys.argv[4])): + sys.exit('Missing destination group %s'%sys.argv[4]) + print copy_task_to_database(sys.argv[2],sys.argv[3],sys.argv[4]) + +## move task or group to database +# @ingroup EDI_CLI +# 2 group<br> +# 3 task|group<br> +# 4 location<br> +# 5 group +def mvb_cli(): + if len(sys.argv) < 5: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + database = sys.argv[3] + destination_group = sys.argv[4] + else: + if len(sys.argv) < 6: + sys.exit('Too little parameters.') + group = sys.argv[2] + tid = sys.argv[3] + database = sys.argv[4] + destination_group = sys.argv[5] + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify parameters + if not database in edi_core.selected: + sys.exit('%s database is not selected.'%database) + # Verify that task tid is not root + if tid == 'root': + sys.exit('root is invalid task') + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not tid in tasks: + sys.exit('%s not found.'%tid) + z = dict(zip(edi_core.selected, edi_core.selected_path)) + data_location = os.path.expanduser(z[database]) + data_location_groups = '%s/groups'%data_location + if not os.path.exists('%s/%s'%(data_location_groups,destination_group)): + sys.exit('Missing destination group %s'%destination_group) + print move_task_to_a_group_to_database(group,tid,database,destination_group) + +## set or show foreground color +# @ingroup EDI_CLI +def fc_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + if len(sys.argv)<4: + color = get_forground_color(sys.argv[2]) + print '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3]) + if len(sys.argv)==4: + set_forground_color(sys.argv[2],sys.argv[3]) + +## set or show background color +# @ingroup EDI_CLI +def bc_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + if len(sys.argv)<4: + color = get_background_color(sys.argv[2]) + print '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3]) + if len(sys.argv)==4: + set_background_color(sys.argv[2],sys.argv[3]) + +## Create database folder and set path in .easydoneit.ini +# @ingroup EDI_CLI +def createDatabase_cli(): + if len(sys.argv) == 4: + name = sys.argv[2] + path = os.path.expanduser(sys.argv[3]) + if name == 'location': + sys.exit('%s is invalid database name.'%name) + if (not os.path.exists(os.path.dirname(path))) and (os.path.dirname(path)): + # os.path.dirname(path) -- to be able to create a database in current directory + sys.exit('%s is unreachable.'%os.path.dirname(path)) +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('data',name,path) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + edi_core.data_location = path + init() + else: + sys.exit('Too little or too many parameters. This command takes 2 parameters: databasename path_to_new_database.') + +## choose or show where to add new tasks +# @ingroup EDI_CLI +def topbot_cli(): + if len(sys.argv)<3: + print 'Add new tasks' + print '%s of group' % edi_core.add_top_or_bottom + else: + if (sys.argv[2]!='bottom') and (sys.argv[2] != 'top'): + sys.exit('%s is invalid parameter. Possible values are "top" or "bottom".'%sys.argv[2]) +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('locations','add_top_or_bottom',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + +## print group in md format +# @ingroup EDI_CLI +def in_cli(): + edi_core.list_option = 'md' + + option = '' + if len(sys.argv) > 2: + if sys.argv[2] == '-A': + # option to create agenda is set + option = 'create agenda' + del sys.argv[2] + + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + if len(sys.argv) == 2: + # no parameter - just command + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + + task_attributes = list_group(group) + if group == 'root': + # for root, first task is the title in the md document + task_attributes[0]['head'] = 'head group' + if option == 'create agenda': + # Generate agenda + for t in task_attributes[1:]: + if t['head'] != 'empty line': + edi_core.agenda.append('%3d - %s %s'%(t['position'],t['group'],t['title'])) + print_list(task_attributes) + return + else: + # default group + group = 'root' + sys.argv.append(group) + # change status because there is no position parameter, no need to search for tid at given position + status = '' + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + if not is_this_task_a_group(sys.argv[2]): + sys.exit('%s is not a group.'%sys.argv[2]) + + task_attributes = list_group(sys.argv[2]) + if sys.argv[2] == 'root': + # for root, first task is the title in the md document + task_attributes[0]['head'] = 'head group' + if option == 'create agenda': + # Generate agenda + for t in task_attributes[1:]: + if t['head'] != 'empty line': + edi_core.agenda.append('%3d - %s %s'%(t['position'],t['group'],t['title'])) + print_list(task_attributes) + +## print group in reStructuredText format +# @ingroup EDI_CLI +def re_cli(): + edi_core.list_option = 'rst' + + option = '' + if len(sys.argv) > 2: + if sys.argv[2] == '-A': + # option to create agenda is set + option = 'create agenda' + del sys.argv[2] + + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + if len(sys.argv) == 2: + # no parameter - just command + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + + task_attributes = list_group(group) + if group == 'root': + # for root, first task is the title in the rst document + task_attributes[0]['head'] = 'head group' + if option == 'create agenda': + # Generate agenda + for t in task_attributes[1:]: + if t['head'] != 'empty line': + # keep only title + edi_core.agenda.append('#. %s'%t['title']) + print_list(task_attributes) + return + else: + # default group + group = 'root' + sys.argv.append(group) + # change status because there is no position parameter, no need to search for tid at given position + status = '' + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + if not is_this_task_a_group(sys.argv[2]): + sys.exit('%s is not a group.'%sys.argv[2]) + + task_attributes = list_group(sys.argv[2]) + if sys.argv[2] == 'root': + # for root, first task is the title in the rst document + task_attributes[0]['head'] = 'head group' + if option == 'create agenda': + # Generate agenda + for t in task_attributes[1:]: + if t['head'] != 'empty line': + # keep only title + edi_core.agenda.append('#. %s'%t['title']) + print_list(task_attributes) + +## show trees +# @ingroup EDI_CLI +# print all trees: group tids and titles +def show_trees_cli(): + +#:loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + print '%s - %s' % (d,path) + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + print ''.join(show_tree()) + +## zip database +# @ingroup EDI_CLI +# The path parameter is optional +# When 2 parameters are given, run data function instead. +def zip_cli(): + + if len(sys.argv) < 3: + data() + return + if len(sys.argv) == 3: + # path is current directory + zip_path = '.' + else: + zip_path = sys.argv[3] + database_name = sys.argv[2] + + # Verify parameters + if not database_name in edi_core.selected: + sys.exit('%s database is not selected.'%database_name) + if not os.path.exists(zip_path): + sys.exit('%s is unreachable.'%zip_path) + + # get database system path + z = dict(zip(edi_core.selected, edi_core.selected_path)) + + # Verify parameters + if not os.path.exists(z[database_name]): + sys.exit('%s is unreachable.'%z[database_name]) + + print 'zipped: %s' % database_name + print 'system path: %s' % z[database_name] + print 'target: %s/%s.tar.bz2' % (zip_path, database_name) + print + #print ('tar -c %s | bzip2 -9 > %s/%s.tar.bz2'%(z[database_name], zip_path, database_name)) + os.system('tar -cj %s > %s/%s.tar.bz2'%(z[database_name], zip_path, database_name)) + +## list autolink groups or set autolink groups +# @ingroup EDI_CLI +def autolink_cli(): + if len(sys.argv)<3: + print 'Autolink groups' + if not edi_core.autolink: + print 'Empty' + else: + print ','.join(edi_core.autolink) + print + print '\n'.join(edi_core.autolink) + if len(sys.argv)==3: +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('locations','autolink',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + +## convert empty group to task +# @ingroup EDI_CLI +def cv_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is not root + if sys.argv[2] == 'root': + sys.exit('root is invalid group') + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + if not is_this_task_a_group(sys.argv[2]): + sys.exit('%s is not a group.'%sys.argv[2]) + print '\n'.join(convert_group_to_task(sys.argv[2])) + +## display user name or set user name +# @ingroup EDI_CLI +def user_cli(): + if len(sys.argv)<3: + print 'User name: %s <%s>'%(edi_core.user,edi_core.email) + if len(sys.argv)>3: + sys.exit('Too many parameters.') + if len(sys.argv)==3: +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('settings','username',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + +## display user email or set user email +# @ingroup EDI_CLI +def email_cli(): + if len(sys.argv)<3: + print 'User name: %s <%s>'%(edi_core.user,edi_core.email) + if len(sys.argv)>3: + sys.exit('Too many parameters.') + if len(sys.argv)==3: +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('settings','useremail',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + +## show statistics +# @ingroup EDI_CLI +def stats_cli(): + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + # Decide what to execute + if len(sys.argv) == 2: + tid = 'root' + if status: + # in tree, set tid to cwd + tid = cwd.split('/')[-1] + if tid == 'tree': + tid = 'root' + exe = 'stats in tree' + else: + exe = 'stats root' + else: + tid = sys.argv[2] + +#:find_task_in_selected_databases_and_setup_data_location + if tid == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + elif tid != 'tree': + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if tid in tasks: + break + exe = 'stats parameter' + + if exe == 'stats root': +#:loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + print '%s - %s' % (d,path) + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + print '\n'.join(statistics('for database')) + else: + if not is_this_task_a_group(tid): + sys.exit('Group %s not found.'%tid) + + print '\n'.join(statistics(tid)) + +## add many tasks or many groups +# @ingroup EDI_CLI +def many_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + + options = '' + group_parameter_status = 'group is a command line parameter' + if len(sys.argv) < 4: + # Parameters are: command filename + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + else: + # default group + group = 'root' + group_parameter_status = 'edi selected group' + + text_file = sys.argv[2] + else: + if ((sys.argv[2] == '-1') or (sys.argv[2] == '-g')) and len(sys.argv) == 4: + options = sys.argv[2] + text_file = sys.argv[3] + # select current group when in tree + if status == 'in tree': + #get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] + #end + else: + # default group + group = 'root' + group_parameter_status = 'edi selected group' + if ((sys.argv[2] == '-1') or (sys.argv[2] == '-g')) and len(sys.argv) == 5: + options = sys.argv[2] + group = sys.argv[3] + text_file = sys.argv[4] + if ((sys.argv[2] != '-1') and (sys.argv[2] != '-g')): + group = sys.argv[2] + text_file = sys.argv[3] + if not options: + exe = 'add' + else: + if options == '-1': + # option -1 adds one task per line in text_file + exe = 'add one task per line' + elif options == '-g': + # option -F stores text_file filename in first line of description + exe = 'add groups from text file' + + if (group == 'root') and (not status): + # set default database when group is root and cwd not in tree +#:set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + if (group != 'root') and ((not status) or (group_parameter_status == 'group is a command line parameter')): +#:find_group_in_selected_databases_and_setup_data_location + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + groups = os.listdir(edi_core.data_location_groups) + if group in groups: + break + + if not is_this_task_a_group(group): + sys.exit('%s is not a group.'%group) + + if not os.path.exists(text_file): + sys.exit('%s unreachable file.'%text_file) + + if exe == 'add': + print add_many_tasks(group,text_file) + if exe == 'add one task per line': + print add_many_one_line_tasks(group,text_file) + if exe == 'add groups from text file': + print add_many_groups_from_text(group,text_file) + +## demo to show how to use Easydoneit CLI +# @ingroup EDI_CLI +def demo_cli(): + print '# Easydoneit CLI - Demo' + # print (), to print without a newline + print ('#'), +#:edi_version + f = open('%s/VERSION'%os.path.abspath(os.path.dirname(sys.argv[0]))) + v = f.readline() + f.close() + print 'Version %s'%v + + print ''' +# Run the commands below in a terminal + +# create the demo database +edi createDatabase easydoneit_cli_demo ~/easydoneit_cli_demo + +# show selected databases - SAVE THIS STRING +edi select + +# show default database - SAVE THIS STRING +edi default + +# select demo database +edi select easydoneit_cli_demo + +# demo database is default +edi default easydoneit_cli_demo + +# the configuration file is in your home, change text editor if you dont use VI +cat ~/.easydoneit.ini + +# create a task in default database, type your task in the text editor +edi cr + +# list tasks +edi ls + +# add task directly from command line +edi add -t demo_task_1 +edi add -t 'demo task 2 - string with spaces' +edi ls + +# tasks can be added on the top of the list +edi topbot top + +# add task from file +echo 'demo task 3' > ~/demo_3.txt +edi add ~/demo_3.txt +rm ~/demo_3.txt +edi ls + +# choose to add tasks at the bottom +edi topbot bottom + +# it is simpler to use Easydoneit CLI in the tree +cd ~/easydoneit_cli_demo/tree +edi ls + +# create a group using position +edi mkdir 0 + +# background color +edi bc 0 230,230,230,255 + +# change order, moves task at position 0 to position 2 +edi order 0 2 + +# change state to done, pending and inactive +edi set 0 1 +edi set 1 3 +edi set 2 4 +edi ls + +# display the complete tree of groups +edi st + +# filter out done tasks +edi stfi done disable +edi ls + +# search +edi search demo + +# delete task +edi rm 1 + +# display system path for a task +edi path 2 + +# generate html +edi html + +# generate reStructuredText (rst) +edi re + +# zip +edi zip easydoneit_cli_demo ~/ + +# delete database, RESELECT YOUR DATABASES WITH edi select BEFORE RUNNING THE COMMANDS BELOW +cd - +edi data -d easydoneit_cli_demo +edi data + +# remove database zip +rm ~/easydoneit_cli_demo.tar.bz2 + +# end + ''' + +## set list of group for edi ls -L, -La and -Lx options +# @ingroup EDI_CLI +def list_cli(): + if len(sys.argv)<3: + print 'list of groups' + if not edi_core.list_of_groups: + print 'Empty' + else: + print ','.join(edi_core.list_of_groups) + print + print '\n'.join(edi_core.list_of_groups) + if len(sys.argv)==3: +#:load_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini')) + # load config from user home + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + config.set('locations','list',sys.argv[2]) +#:save_ini_file + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + config.write(f) + f.close() + +## copy database sys.argv[2] to database sys.argv[3] +# @ingroup EDI_CLI +def merge_cli(): + if len(sys.argv) < 4: + sys.exit('Too little parameters.') + + # set source path (argv 2) + path = '' + if os.path.exists(sys.argv[2]): + path = sys.argv[2] + if not path: + # argv 2 could be a database name + dbs = dict(edi_core.databases) + if sys.argv[2] in dbs.keys(): + path = dbs[sys.argv[2]] + if not path: + sys.exit('Source database %s not found.'%sys.argv[2]) + + # check source database structure + directories = ['tasks', 'groups', 'tree'] + + status = 'source database structure ok' + for d in directories: + directory = '%s/%s'%(path, d) + if not os.path.exists(directory): + print 'Missing %s'%directory + status = 'missing directories in source database' + if status == 'missing directories in source database': + sys.exit('Invalid source database %s'%path) + + # check destination database + if not sys.argv[3] in edi_core.selected: + sys.exit('%s database not selected.'%sys.argv[3]) + + s = dict(zip(edi_core.selected, edi_core.selected_path)) + destination_path = s[sys.argv[3]] + + # copy database + for d in directories: + directory = '%s/%s'%(path, d) + ddirectory = '%s/%s'%(destination_path, d) + os.system('cp -R %s/* %s'%(directory,ddirectory)) + + +## copy media sys.argv[3] to task sys.argv[2] +# @ingroup EDI_CLI +def media_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + if len(sys.argv)<4: + print '\n'.join(get_media(sys.argv[2])) + if len(sys.argv)==4: + if not os.path.exists(sys.argv[3]): + sys.exit('%s not found.'%sys.argv[3]) + print set_media(sys.argv[2],sys.argv[3]) + + +## copy attachments sys.argv[3] to task sys.argv[2] +# @ingroup EDI_CLI +def at_cli(): + if len(sys.argv) < 3: + sys.exit('Too little parameters.') + # Figure out if the command is run in the tree +#:figure_out_if_in_tree + status = '' + cwd = os.path.realpath(os.getcwd()) + #loop_on_selected_databases + z = zip(edi_core.selected, edi_core.selected_path) + for d,path in z: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + # check path from left + real_tree_path = os.path.realpath(edi_core.data_location_tree) + if real_tree_path == cwd[:len(real_tree_path)]: + status = 'in tree' + p_l = edi_core.data_location_tree.split('/')[-2:] + break + +#:find_task_sysargv2_in_selected_databases + # the goal for this code is to reset status when a tid is an existing task + # when parameter is taskid, it doesnt matter if cwd is in tree + # find task in selected databases + for path in edi_core.selected_path: + data_location = os.path.expanduser(path) + data_location_tasks = '%s/tasks'%data_location + + if not os.path.exists(data_location): + sys.exit('%s is unreachable.'%path) + tasks = os.listdir(data_location_tasks) + if sys.argv[2] in tasks: + # found task, setup database below + status = '' + break + + if status == 'in tree': +#:get_current_group_in_tree + if cwd.split('/')[-1] == 'tree': + group = 'root' + else: + group = cwd.split('/')[-1] +#:get_position_from_argv2 + try: + position = int(sys.argv[2]) + except: + sys.exit('%s is invalid position'%sys.argv[2]) +#:find_task_at_position + if position < 0: + sys.exit('Position %d is invalid.'%position) + group_tasks = os.listdir(generate_group_path(group)) + order_id = baseconvert(position) + tid = '' + for t in group_tasks: + # find exact match + if order_id == t[:ORDER_ID_LENGTH]: + tid = t[ORDER_ID_LENGTH:] + if not tid: + sys.exit('Position %d does not exist.'%position) + sys.argv[2] = tid + + # cant cover sys.exit('%s is unreachable.'%d) below, exception catched before. Dont add no cover because the code is reused in other functions. +#:tab1_find_task_in_selected_databases_and_setup_data_location + if sys.argv[2] == 'root': + #set_default_database + z = dict(zip(edi_core.selected, edi_core.selected_path)) + edi_core.data_location = os.path.expanduser(z[edi_core.default_add_in]) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) + else: + for path in edi_core.selected_path: + edi_core.data_location = os.path.expanduser(path) + edi_core.data_location_tasks = '%s/tasks'%edi_core.data_location + edi_core.data_location_groups = '%s/groups'%edi_core.data_location + edi_core.data_location_tree = '%s/tree'%edi_core.data_location + + if not os.path.exists(edi_core.data_location): + sys.exit('%s is unreachable.'%edi_core.data_location) #pragma: no cover + tasks = os.listdir(edi_core.data_location_tasks) + if sys.argv[2] in tasks: + break + # Verify that task tid is in database + tasks = os.listdir(edi_core.data_location_tasks) + if not sys.argv[2] in tasks: + sys.exit('%s not found.'%sys.argv[2]) + if len(sys.argv)<4: + print '\n'.join(get_attachments(sys.argv[2])) + if len(sys.argv)>3: + for fn in sys.argv[3:]: + if not os.path.exists(fn): + sys.exit('%s not found.'%fn) + print '\n'.join(set_attachments(sys.argv[2],sys.argv[3:])) + + +## CLI command functions +FUNCTIONS = [help,version,create_task_cli,lsid,add_task_cli,cat,mkdir,show_group_for_task_cli,rm,vi,change_order,set,reset,search,move_task,copy_task,link_task,get_task_path,tree,ls,set_status_filter,data,html_cli,selected_cli,default_cli,cpb_cli,mvb_cli,fc_cli,bc_cli,createDatabase_cli,topbot_cli,in_cli,re_cli,show_trees_cli,zip_cli,autolink_cli,cv_cli,user_cli,email_cli,stats_cli,many_cli,demo_cli,list_cli,merge_cli,media_cli,at_cli] + +COMMAND_MAP = dict(zip(COMMANDS,FUNCTIONS)) + + +if __name__ == '__main__': +#:print_command_map +#:end + start() #pragma: no cover + + if len(sys.argv)> 1: #pragma: no cover + # remove whitespaces from parameters. ^M can come for scripts + for i,a in enumerate(sys.argv[1:]): #pragma: no cover + sys.argv[i+1] = a.strip() #pragma: no cover +#:not profiler + COMMAND_MAP.get(sys.argv[1],Nothing)() #pragma: no cover +#:else +#:end + else: + help() #pragma: no cover +#:profiler +#:end diff --git a/edi_common.py b/edi_common.py @@ -0,0 +1,72 @@ +#!/usr/local/util/bin/python +# +## @package edi_common +# Common data and functions for Easydoneit CLI. +# python style<br> +# basic functions that can be reused outside easydoneit<br> +# +# Copyright (C) 2014 Spartatek AB +# +# contact@spartatek.se +# http://spartatek.se +# +# EASYDONEIT CLI is free software: you can redistribute it and/or modify +# it under the terms of the GNU Genereric Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# EASYDONIT CLI is distributed in the hope that it will be usesul, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Genereral Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# GNU licences http://www.gnu.org/licenses + +import os +import logging + +#========================================================================== +# Setup logger +#========================================================================== +if __name__ == '__main__': + console = logging.StreamHandler() #pragma: no cover + formatter = logging.Formatter('%(levelname)-8s (%(name)s) %(message)s') #pragma: no cover + console.setFormatter(formatter) #pragma: no cover + console.setLevel(logging.INFO) #pragma: no cover + logging.getLogger('').addHandler(console) #pragma: no cover +logger = logging.getLogger(os.path.basename(__file__)) +logger.setLevel(logging.INFO); +#========================================================================== +# Logger has been set up +#========================================================================== + + + +#========================================================================== + +## Find all files in tree path. +# @return list of paths for files +# @param[in] path to tree +# @ingroup EDI_CORE +def ffind(path, namefs=None, relative=True): + """ + Finds files in the directory tree starting at 'path' (filtered by the + functions in the optional 'namefs' sequence); if the 'relative' + flag is not set, the result sequence will contain absolute paths. + + Returns a sequence of paths for files found. + """ + if not os.access(path, os.R_OK): + logger.error("cannot access path: '%s'" % path) #pragma: no cover + + fileList = [] + try: + for dir, subdirs, files in os.walk(path): + fileList.extend(['%s%s%s' % (dir, os.sep, f) for f in files]) + if not relative: fileList = map(os.path.abspath, fileList) + if namefs: + for ff in namefs: fileList = filter(ff, fileList) #pragma: no cover + except Exception, e: logger.error(str(e)) #pragma: no cover + return(fileList) diff --git a/edi_core.py b/edi_core.py @@ -0,0 +1,2673 @@ +#! /usr/bin/env python +# -*- coding: latin-1 -*- +## @package edi_core +# Core module +# +# +# Copyright (C) 2014 Spartatek AB +# +# contact@spartatek.se +# http://spartatek.se +# +# EASYDONEIT CLI is free software: you can redistribute it and/or modify +# it under the terms of the GNU Genereric Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# EASYDONIT CLI is distributed in the hope that it will be usesul, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Genereral Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# GNU licences http://www.gnu.org/licenses + +import sys +import re +import time +import shutil +import os +import ConfigParser +from string import ascii_lowercase +from string import ascii_uppercase +from string import digits +import random +import stat +import getpass + +from edi_common import * + +## user_interface is initialized in start() and changes the behavior of some functions to allow a functional user interface design. The default is not used, start() is always called before using edi_core. +# Values: 'cli', 'web' +user_interface = 'cli' + +data_location = '' +data_location_tasks = '' +data_location_groups = '' +data_location_tree = '' + +## Available databases +databases = [] + +## list selected databases +selected = [] +selected_path = [] +## default database where tasks are created +default_add_in = '' + +## add new tasks in 'bottom' of group or 'top' of group +add_top_or_bottom = 'bottom' + +## Autlink groups (array of tids) +autolink = '' + +## list of groups for edi ls -L, -La and -Lx (array of tids) +list_of_groups = '' + +## characters for task ids +ID_BASE = sorted(list(digits)+list(ascii_lowercase)+list(ascii_uppercase)+['_',',']) +ID_BASE_STRING = ''.join(ID_BASE) +## Length of task id +ID_LENGTH = 16 +## Length of order id in groups +ORDER_ID_LENGTH = 8 +BASE = len(ID_BASE) + +TASK_STATUS = [' Active', + ' Done', + ' Ongoing', + ' Pending', + 'Inactive', + ' Void'] +TASK_STATUS_ACTIVE = 0 +TASK_STATUS_DONE = 1 +TASK_STATUS_ONGOING = 2 +TASK_STATUS_PENDING = 3 +TASK_STATUS_INACTIVE = 4 +TASK_STATUS_VOID = 5 + +## Sort order for sort_task_attributes function, ongoing, active, pending, done, inactive, void +SORT_TASK_ORDER = [2,0,3,1,4,5] + +LIST_OPTIONS = ['tids', + 'positions', + 'html'] +list_option = 'tids' + +STATUS_FILTER_STATES = ['enable','disable'] + +## enables all status filters +status_filters = [STATUS_FILTER_STATES[0] for i in range(len(TASK_STATUS))] +## status filter dictionary, initialized after loading ini file +status_filters_d = {} + +## colors +no_color = (-1,-1,-1,255) +#status_fgColors = [(0,0,0,255) for i in range(len(TASK_STATUS))] +status_fgColors = [(0,0,0,255),(0,255,0,255),(255,128,0,255),(160,32,240,255),(192,192,192,255),(0,0,0,255)] +status_fgColors_d = {} +## no background color by default +status_bgColors = [no_color for i in range(len(TASK_STATUS))] +status_fgColors_d = {} + +## text editor in terminal - easydoneit.ini [settings] EDITOR +editor = 'vi' + +## Agenda generated by edi.in_cli +agenda = [] + +## user name +user = '' + +## user email +email = '' + +## stats for statistics function +stats = {} + +## total number of tasks for statistics function +stats_total = 0 + +## creation dates of tasks in statistics, used to find out oldest task +stats_creation_dates = [] + +## timely state changes and creation dates +# Type: dict of dict of integers +# keys are dates +# each dates is a dictionary with STATS_OVERTIME_KEYS keys +# each key is the amount for states and creation +stats_overtime = {} + +## timely state changes and creation dates +STATS_OVERTIME_KEYS = [TASK_STATUS[i] for i in range(len(TASK_STATUS))] + ['Creation'] + +## Group directory, cache result to speed up color functions +group_directory_file_list = [] + +## initializes path to data. +# creates directories<br> +# creates root group and task<br> +# @ingroup EDI_CORE +def init(): + # Set global variables + global data_location_tasks + global data_location_groups + global data_location_tree + global status_filters + global status_filters_d + global status_fgColors + global status_fgColors_d + global status_bgColors + global status_bgColors_d + + status_filters_d = dict(zip(TASK_STATUS,status_filters)) + status_fgColors_d = dict(zip(TASK_STATUS,status_fgColors)) + status_bgColors_d = dict(zip(TASK_STATUS,status_bgColors)) + + data_location_tasks = '%s/tasks'%data_location + data_location_groups = '%s/groups'%data_location + data_location_tree = '%s/tree'%data_location + + if not os.path.exists(data_location_tasks): + if not os.path.exists(data_location): + os.mkdir(data_location) + os.mkdir(data_location_tasks) + os.mkdir(data_location_groups) + os.mkdir(data_location_tree) + os.mkdir('%s/root'%data_location_groups) + + # Create root description in tasks + task_path = '%s/root'%data_location_tasks + os.mkdir(task_path) + # copy root description from script directory + shutil.copy('%s/root.txt'%os.path.abspath(os.path.dirname(sys.argv[0])),'%s/description.txt'%task_path) + + # Create status + f = open('%s/status'%task_path,'w') + f.write(TASK_STATUS[TASK_STATUS_VOID]) + f.close() + +## log actions in database - data_location/log.txt +# @param[in] s string without user name and timestamp +# @ingroup EDI_CORE +def edi_log(s): + global user + global email + f = open('%s/log.txt'%data_location,'a') + f.write('%s - %s - %s\n'%(time.strftime("%Y-%m-%d %H:%M"), '%s <%s>'%(user,email),s.strip())) + f.close() + + +## select location database +# @param[in] location database name +# @ingroup EDI_CORE +def select_database(location): + global data_location + global data_location_tasks + global data_location_groups + global data_location_tree + + z = dict(zip(selected, selected_path)) + data_location = os.path.expanduser(z[location]) + data_location_tasks = '%s/tasks'%data_location + data_location_groups = '%s/groups'%data_location + data_location_tree = '%s/tree'%data_location + if not os.path.exists(data_location): + return '%s is unreachable.'%data_location + else: + return '' + +## Mix foreground colors from task, groups and default colors. +# @return color array +# @param[in] tid task id +# @ingroup EDI_CORE +# Collect all defined colors in link groups. +# For each group, the group gets the first color defined in the tree<br> +# compute average color +def mix_fgcolors(tid): + # mix colors + # collect all defined colors in groups to root + colors = [] + # root has no parent group, dont search parent group color + if tid != 'root': + # find all groups - link and parent group + if is_linked(tid): + groups = os.listdir('%s/groups/' % generate_task_path(tid)) + else: + groups = [find_group_containing_task(tid)] + + # walk parent groups until a color or root is found + while groups: + status_color = 'search' + if os.path.isfile('%s/fgColor'%generate_task_path(groups[0])): + f = open('%s/fgColor'%generate_task_path(groups[0])) + c = tuple([int(i) for i in f.readline().split(',')]) + f.close() + # -1 means no_color, mix colors + if c[0] != -1: + colors.append(c) + status_color = 'found color' + if status_color == 'search': + parent_group = find_group_containing_task(groups[0]) + if parent_group != 'root': + groups.append(parent_group) + del groups[0] + + # compute average color + if colors: + color = [0,0,0,0] + for c in colors: + for i in range(len(c)): + color[i] += c[i] + colorCount = len(colors) + color = tuple([i/colorCount for i in color]) + if not colors: + # no defined colors, use default color for status + task_status = get_status(tid) + color = status_fgColors_d[task_status] + return color + +## get color for task tid +# @return color array +# @param[in] tid task id +# @ingroup EDI_CORE +# when no color is set, mix colors from groups or use defaults +def get_forground_color(tid): + color = no_color + + if os.path.isfile('%s/fgColor'%generate_task_path(tid)): + f = open('%s/fgColor'%generate_task_path(tid)) + color = tuple([int(i) for i in f.readline().split(',')]) + f.close() + # -1 means no_color, mix colors + if color[0] == -1: + color = mix_fgcolors(tid) + else: + color = mix_fgcolors(tid) + return color + +## set color for task tid +# @return color array +# @param[in] tid task id +# @param[in] color_s color array +# @ingroup EDI_CORE +def set_forground_color(tid,color_s): + f = open('%s/fgColor'%generate_task_path(tid),'w') + f.write(color_s) + f.close() + edi_log('set foreground color in %s to %s'%(tid,color_s)) + +## Mix background colors from task, groups and default colors. +# @return color array +# @param[in] tid task id +# @ingroup EDI_CORE +# Collect all defined colors in link groups. +# For each group, the group gets the first color defined in the tree<br> +# compute average color +def mix_bgcolors(tid): + # mix colors + # collect all defined colors in groups to root + colors = [] + # root has no parent group, dont search parent group color + if tid != 'root': + # find all groups - link and parent group + if is_linked(tid): + groups = os.listdir('%s/groups/' % generate_task_path(tid)) + else: + groups = [find_group_containing_task(tid)] + + # walk parent groups until a color or root is found + while groups: + status_color = 'search' + if os.path.isfile('%s/bgColor'%generate_task_path(groups[0])): + f = open('%s/bgColor'%generate_task_path(groups[0])) + c = tuple([int(i) for i in f.readline().split(',')]) + f.close() + # -1 means no_color, mix colors + if c[0] != -1: + colors.append(c) + status_color = 'found color' + if status_color == 'search': + parent_group = find_group_containing_task(groups[0]) + if parent_group != 'root': + groups.append(parent_group) + del groups[0] + + # compute average color + if colors: + color = [0,0,0,0] + for c in colors: + for i in range(len(c)): + color[i] += c[i] + colorCount = len(colors) + color = tuple([i/colorCount for i in color]) + if not colors: + # no defined colors, use default color for status + task_status = get_status(tid) + color = status_bgColors_d[task_status] + return color + +## get color for task tid +# @return color array +# @param[in] tid task id +# @ingroup EDI_CORE +# when no color is set, mix colors from groups or use defaults +def get_background_color(tid): + color = no_color + + if os.path.isfile('%s/bgColor'%generate_task_path(tid)): + f = open('%s/bgColor'%generate_task_path(tid)) + color = tuple([int(i) for i in f.readline().split(',')]) + f.close() + # -1 means no_color, mix colors + if color[0] == -1: + color = mix_bgcolors(tid) + else: + color = mix_bgcolors(tid) + return color + +## set color for task tid +# @return color array +# @param[in] tid task id +# @param[in] color_s color array +# @ingroup EDI_CORE +def set_background_color(tid,color_s): + f = open('%s/bgColor'%generate_task_path(tid),'w') + f.write(color_s) + f.close() + edi_log('set background color in %s to %s'%(tid,color_s)) + +## converts color to hex format +# @return color hex string +# @param[in] color color array +# @ingroup EDI_CORE +def color_to_hex(color): + c = [] + for n in color: + if n == -1: + # -1 is no color, white + c.append(255) + else: + c.append(n) + color = tuple(c) + return '%02x%02x%02x' % (color[0],color[1],color[2]) + +## filesystem path for task in database tasks +# @return path +# @param[in] tid task id +# @ingroup EDI_CORE +def generate_task_path(tid): + return '%s/%s'%(data_location_tasks,tid) + +## filesystem path for group in database groups +# @return path +# @param[in] tid task id +# @ingroup EDI_CORE +def generate_group_path(tid): + return '%s/%s/'%(data_location_groups,tid) + +## get status for tid +# @return status string +# @param[in] tid task id +# @ingroup EDI_CORE +def get_status(tid): + # open status file + try: + f = open('%s/status'%generate_task_path(tid)) + status = f.readline() + f.close() + except: + print '%s is an invalid task.'%generate_task_path(tid) + status = TASK_STATUS[TASK_STATUS_VOID] + return status + +## creates a string with task id, status and string parameter +# @return list of strings +# @param[in] tid task id +# @param[in] s task title string +# @ingroup EDI_CORE +# Displays a task depending on edi_core.list_option +def generate_task_string_with_tid(tid,s): + status = get_status(tid) + if list_option == 'tids': + # %*s to dynamically adjust id length + r = '%*s - %s - %s'%(ID_LENGTH,tid,status,s) + if list_option == 'positions': + if is_this_task_a_group(tid): + # print tid for groups in the list + r = '%*s - %s - %s'%(ID_LENGTH,tid,status,s) + else: + tid = ' ' + r = '%*s %s - %s'%(ID_LENGTH,tid,status,s) + if list_option == 'md': + # print title and description + f = open('%s/description.txt'%generate_task_path(tid)) + # remove title line + description = ''.join(f.readlines()[1:]) + f.close() + + tid = ' ' + # add <br> for long titles + r = '\n---\n#%s<br>\n%s'%(s,description) + if list_option == 'rst': + # print title and description + f = open('%s/description.txt'%generate_task_path(tid)) + # remove title line + description_l = f.readlines()[1:] + f.close() + + # convert urls to link + NOBRACKET = r'[^\]\[]*' + BRK = ( r'\[(' + + (NOBRACKET + r'(\[')*6 + + (NOBRACKET+ r'\])*')*6 + + NOBRACKET + r')\]' ) + NOIMG = r'(?<!\!)' + LINK_RE = NOIMG + BRK + \ + r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' + # [text](url) or [text](<url>) or [text](url "title") + + compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE) + + for line_index,l in enumerate(description_l): + if 'http' in l: + try: + match = compiled_re.match(l) + url = match.group(9) + text = match.group(2) + before_url = match.group(1) + #title = match.group(13) + after_url = match.group(14) + + l = '%s`%s<%s>`_%s'% (before_url, text, url, after_url) + except: + # not used, information + link_status = 'WARNING: the link is not in format [title](url)' + description_l[line_index] = l + + description = ''.join(description_l) + + tid = ' ' + # remove position and type (GROUP, LINK) from title + title = s[11:].lstrip() + r = '%s\n'%title + '='*len(title) + '\n%s\n\n'%description + if list_option == 'html': + #<tr bgcolor="#FEFFCC"> + # <td class="subject"> <font color="#000000">t1</font></td> + # <td class="description"> <font color="#000000">&nbsp;</font></td> + #</tr> +#:define read_description + # convert < to &lt; and > &gt; to ignore html tags in title line + s = s.replace('<','&lt;').replace('>','&gt;') + f = open('%s/description.txt'%generate_task_path(tid)) + # remove title line, convert < to &lt; and > &gt; to ignore html tags + description_l = ['%s<br>'%i.rstrip().replace('<','&lt;').replace('>','&gt;') for i in f.readlines()[1:]] + f.close() + + # convert urls to link + NOBRACKET = r'[^\]\[]*' + BRK = ( r'\[(' + + (NOBRACKET + r'(\[')*6 + + (NOBRACKET+ r'\])*')*6 + + NOBRACKET + r')\]' ) + NOIMG = r'(?<!\!)' + LINK_RE = NOIMG + BRK + \ + r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' + # [text](url) or [text](<url>) or [text](url "title") + + compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE) + + for line_index,l in enumerate(description_l): + if 'http' in l: + try: + match = compiled_re.match(l) + url = match.group(9) + text = match.group(2) + before_url = match.group(1) + #title = match.group(13) + after_url = match.group(14) + + l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url) + except: + # not used, information + link_status = 'WARNING: the link is not in format [title](url)' + description_l[line_index] = l + + description = '\n'.join(description_l) +#:end + + if is_this_task_a_group(tid) and user_interface == 'web': + subject = '<a href="edi_web.py?tid=%s">%s - %s</a>'%(tid,status,s) + else: + subject = '%s - %s'%(status,s) + fg = color_to_hex(get_forground_color(tid)) + bg = color_to_hex(get_background_color(tid)) + r=' <tr bgcolor="#%s">\n <td class="subject"> <font color="#%s">%s</font></td>\n <td class="description"> <font color="#%s">%s</font></td>\n </tr>' % (bg, fg, subject, fg, description) + return r + +## creates a string with task id, status and string parameter +# @return list of strings +# @param[in] tid task id +# @param[in] s task title string +# @ingroup EDI_CORE +# Displays a group depending on edi_core.list_option +def generate_group_string_with_tid(tid,s): + global agenda + + status = get_status(tid) + if (list_option == 'tids') or (list_option == 'positions'): + # %*s to dynamically adjust id length + r = '%*s - %s - %s'%(ID_LENGTH,tid,status,s) + if list_option == 'md': + tid = ' ' + # print group title only + r = '#%s'%s.replace(' 0 - GROUP','') + if agenda: + r += '\n---\n# Agenda\n%s' % '\n\n'.join(agenda) + if list_option == 'rst': + tid = ' ' + # print group title only + title = s.replace(' 0 - GROUP','') + r = '='*len(title) + '\n%s\n'%title + '='*len(title) + '\n\n' + if agenda: + r += 'Agenda\n======\n%s\n\n' % '\n'.join(agenda) + else: + r += '\n.. contents::\n\n' + if list_option == 'html': + #<tr bgcolor="#FEFFCC"> + # <td class="subject"> <font color="#000000">t1</font></td> + # <td class="description"> <font color="#000000">&nbsp;</font></td> + #</tr> + # display description in html +#:read_description + # convert < to &lt; and > &gt; to ignore html tags in title line + s = s.replace('<','&lt;').replace('>','&gt;') + f = open('%s/description.txt'%generate_task_path(tid)) + # remove title line, convert < to &lt; and > &gt; to ignore html tags + description_l = ['%s<br>'%i.rstrip().replace('<','&lt;').replace('>','&gt;') for i in f.readlines()[1:]] + f.close() + + # convert urls to link + NOBRACKET = r'[^\]\[]*' + BRK = ( r'\[(' + + (NOBRACKET + r'(\[')*6 + + (NOBRACKET+ r'\])*')*6 + + NOBRACKET + r')\]' ) + NOIMG = r'(?<!\!)' + LINK_RE = NOIMG + BRK + \ + r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' + # [text](url) or [text](<url>) or [text](url "title") + + compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE) + + for line_index,l in enumerate(description_l): + if 'http' in l: + try: + match = compiled_re.match(l) + url = match.group(9) + text = match.group(2) + before_url = match.group(1) + #title = match.group(13) + after_url = match.group(14) + + l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url) + except: + # not used, information + link_status = 'WARNING: the link is not in format [title](url)' + description_l[line_index] = l + + description = '\n'.join(description_l) + + subject = '%s - %s'%(status, s) + fg = color_to_hex(get_forground_color(tid)) + bg = color_to_hex(get_background_color(tid)) + r=' <tr bgcolor="#%s">\n <td class="subject"> <font color="#%s">%s</font></td>\n <td class="description"> <font color="#%s">%s</font></td>\n </tr>' % (bg, fg, subject, fg, description) + return r + +## determines if task is a group +# @return empty string or 'this task is a group' +# @param[in] tid task id +# @ingroup EDI_CORE +def is_this_task_a_group(tid): + # Identify if task is a group + is_a_group = '' + # list groups + groups = os.listdir(data_location_groups) + if tid in groups: + is_a_group = 'this task is a group' + return is_a_group + +## get task title +# @return first line of task description +# @param[in] tid task id +# @ingroup EDI_CORE +def get_task_title(tid): + task_path = generate_task_path(tid) + f = open('%s/description.txt'%task_path) + r = f.readline().strip() + f.close() + return r + +## get creation date +# @return array of date string and unix time +# @param[in] tid task id +# @ingroup EDI_CORE +def get_creation_date(tid): + r = [] + # Figure out creation time and modification times for description and status + task_path = generate_task_path(tid) + if not os.path.exists('%s/ctime.txt'%task_path): + task_ctime = 'Not available' + r.append(task_ctime) + r.append(0) + else: + f = open('%s/ctime.txt'%task_path) + task_ctime = f.readline() + f.close() + r.append(task_ctime) + r.append(time.mktime(time.strptime(task_ctime))) + + return r + +## get media +# @return media file name +# @param[in] tid task id +# @ingroup EDI_CORE +def get_media(tid): + r = ['None'] + task_path = generate_task_path(tid) + if os.path.exists('%s/media'%task_path): + files = os.listdir('%s/media'%task_path) + r = [files[0].split('.')[0], '%s/media/%s'%(task_path,files[0])] + return r + +## set media, one media file per task +# @return media file name +# @param[in] tid task id +# @param[in] filename media file to copy to task +# @ingroup EDI_CORE +def set_media(tid,filename): + r = '%s is not a supported media file.' % filename + + task_path = generate_task_path(tid) + if filename.strip()[-3:] == 'wav': +#:define create_media_folder + if not os.path.exists('%s/media'%task_path): + # create media folder + os.mkdir('%s/media'%task_path) +#:end + else: + # media exists, check type + files = os.listdir('%s/media'%task_path) + if not files[0][-3:] == 'wav': + r = '%s is not a sound.'%tid + return r + # sound file + shutil.copy(filename,'%s/media/sound.wav'%task_path) + r = 'sound.wav' + if filename.strip()[-3:] == 'jpg': +#:create_media_folder + if not os.path.exists('%s/media'%task_path): + # create media folder + os.mkdir('%s/media'%task_path) + else: + # media exists, check type + files = os.listdir('%s/media'%task_path) + if not files[0][-3:] == 'jpg': + r = '%s is not an image.'%tid + return r + # image file + shutil.copy(filename,'%s/media/image.jpg'%task_path) + r = 'image.jpg' + edi_log('added media %s in task %s'%(filename,tid)) + return r + +## get attachments +# @return attachment file names +# @param[in] tid task id +# @ingroup EDI_CORE +def get_attachments(tid): + r = ['None'] + task_path = generate_task_path(tid) + if os.path.exists('%s/attachments'%task_path): + files = os.listdir('%s/attachments'%task_path) + r = ['%s/attachments/%s'%(task_path,i) for i in files] + return r + +## set attachments +# @return attachment file names +# @param[in] tid task id +# @param[in] array of filenames +# @ingroup EDI_CORE +# With *, set_attachments copies multiple files at once +def set_attachments(tid,filenames): + task_path = generate_task_path(tid) + if not os.path.exists('%s/attachments'%task_path): + # create attachment folder + os.mkdir('%s/attachments'%task_path) + + r = [] + for fn in filenames: + try: + shutil.copy(fn,'%s/attachments/'%task_path) + edi_log('added attachment %s in task %s'%(fn,tid)) + # Remove path from fn, keep only filename + r.append('%s/attachments/%s'%(task_path,fn.split(os.sep)[-1])) + except: + r.append('Failed to copy %s'%fn) + return r + +## sort task attributes (ls in edi cli) +# @return sorted task list by state +# @param[in] result from list_group +# @ingroup EDI_CORE +def sort_task_attributes(task_attributes): + r = [] + # Keep head group on top + if task_attributes[0]['head'] == 'head group': + r.append(task_attributes[0]) + del task_attributes[0] + for s in SORT_TASK_ORDER: + for t in task_attributes: + if t['status'] == TASK_STATUS[s]: + r.append(t) + return r + +## sort function for sorting an array of tasks by date from newest to oldest +# @return compare result +# @param[in] result from list_group +# @ingroup EDI_CORE +def sortdatefunc(x,y): + return cmp(y['ctime'],x['ctime']) + +## sort task attributes (ls in edi cli) +# @return sorted task list by date +# @param[in] result from list_group +# @ingroup EDI_CORE +def sort_task_attributes_by_date(task_attributes): + r = [] + # Keep head group on top + if task_attributes[0]['head'] == 'head group': + r.append(task_attributes[0]) + del task_attributes[0] + + # -1 to exclude the empty line + a = task_attributes[:-1] + a.sort(sortdatefunc) + #print task_attributes + r += a + r.append(task_attributes[-1]) + return r + +## list id - status - group - first line from description +# @return list of tasks and groups title +# @param[in] tid task id +# @ingroup EDI_CORE +# items in groups have the format 'ORDER_ID''TASK_ID'<br> +# task 0 is group tittle<br> +#<br> +# result array elements:<br> +# {head :string, tid :string, position :value, group :string, title :string, status :string}<br> +# head tells if the element is a group title.<br> +#<br> +# example:<br> +#fTTB1KRWfDpoSR1_ - Active - GROUP Motivational Interviewing<br> +#62ZvFA_q0pCZFr0Y - Active - news<br> +def list_group(tid): + result = [] + # list visible groups only + task_status = get_status(tid) + if status_filters_d[task_status] == STATUS_FILTER_STATES[0]: + # List task titles in group + group_path = generate_group_path(tid) + + # List groups to indicate when a task is a group + # Identify if task is a group + groups = os.listdir(data_location_groups) + # End Identify if task is a group + + # print items in tid in order + for n,fn in enumerate(sorted(os.listdir(group_path))): + # print position in list + current_postion = baseconvert_to_dec(fn[:ORDER_ID_LENGTH]) + + # Identify if task is a group + # Remove order_id, keep task id only + group = ' ' + if fn[ORDER_ID_LENGTH:] in groups: + group = 'GROUP' + if is_linked(fn[ORDER_ID_LENGTH:]): + group = ' LINK' + # End Identify if task is a group + # Get task title + # Remove order_id, keep task id only + task_path = generate_task_path(fn[ORDER_ID_LENGTH:]) + f = open('%s/description.txt'%task_path) + # Remove order_id, keep task id only + if (not n) and (not tid == 'root'): + # First task is group title + # filter status, keep task if status filter is enabled + task_status = get_status(fn[ORDER_ID_LENGTH:]) + if status_filters_d[task_status] == STATUS_FILTER_STATES[0]: + task_d = {} + task_d['head'] = 'head group' + task_d['tid'] = fn[ORDER_ID_LENGTH:] + task_d['position'] = current_postion + task_d['group'] = group + task_d['title'] = f.readline().strip() + task_d['status'] = get_status(task_d['tid']) + task_d['ctime'] = get_creation_date(task_d['tid'])[1] + result.append(task_d) + else: + # filter status, keep task if status filter is enabled + task_status = get_status(fn[ORDER_ID_LENGTH:]) + if status_filters_d[task_status] == STATUS_FILTER_STATES[0]: + task_d = {} + task_d['head'] = 'element' + task_d['tid'] = fn[ORDER_ID_LENGTH:] + task_d['position'] = current_postion + task_d['group'] = group + task_d['title'] = f.readline().strip() + task_d['status'] = get_status(task_d['tid']) + task_d['ctime'] = get_creation_date(task_d['tid'])[1] + result.append(task_d) + f.close() + task_d = {} + task_d['head'] = 'empty line' + task_d['tid'] = '' + task_d['position'] = 0 + task_d['group'] = '' + task_d['title'] = '' + task_d['status'] = '' + task_d['ctime'] = 0 + result.append(task_d) + return result + +## lists all items in the tid group +# @return list of tasks and groups title +# @param[in] tid task id +# @ingroup EDI_CORE +def list_tree(tid): + result = [] + # walk_group is the list of groups to visit. FIFO + walk_group = [tid] + # the while loop goes through all the group that are found + while walk_group: + # list visible groups only + task_status = get_status(walk_group[0]) + if status_filters_d[task_status] == STATUS_FILTER_STATES[0]: + # list items in first group + tasks = sorted(os.listdir(generate_group_path(walk_group[0]))) + result += list_group(walk_group[0]) + + # add group found in first group + for t in tasks: + # Check tasks that are not title task in a group + if (t[ORDER_ID_LENGTH:] != walk_group[0]) and is_this_task_a_group(t[ORDER_ID_LENGTH:]): + walk_group.append(t[ORDER_ID_LENGTH:]) + + # remove first group to list items in next group + del walk_group[0] + return result + +## generates a random task id +# @return new task id +# @ingroup EDI_CORE +def generate_id(): + return ''.join([random.choice(ID_BASE) for _ in range(ID_LENGTH)]) + +## converts decimal number to BASE (base 65) +# @return string representing a number in base BASE +# @param[in] n integer +# @ingroup EDI_CORE +def baseconvert(n): + s = "" + while 1: + r = n % BASE + s = ID_BASE[r] + s + n = n / BASE + if n == 0: + break + + s = s.rjust(ORDER_ID_LENGTH, ',') + return s + +## converts BASE number to decimal +# @return n integer +# @param[in] n string representing a number in base BASE +# @ingroup EDI_CORE +def baseconvert_to_dec(n): + r = 0 + power = 1 + for digit in reversed(list(n)): + r += ID_BASE_STRING.find(digit) * power + power *= BASE + return r + +## add task to group folder in database groups +# @param[in] tid task id +# @param[in] group task id +# @ingroup EDI_CORE +# Tasks are added at the top or bottom of the list. +def add_task_to_group_folder(tid,group): + # Create an entry in group + tasks = sorted(os.listdir(generate_group_path(group))) + # Add +1 to last order_id to have the task last in the list + if tasks: + if (len(tasks) == 1) or (add_top_or_bottom == 'bottom'): + # add tasks in bottom + order_id = baseconvert(baseconvert_to_dec(tasks[-1][:ORDER_ID_LENGTH])+1) + else: + # add tasks on top + # temporary orderid # + orderid_and_tid = '#' * ORDER_ID_LENGTH + tid + if group == 'root': + to_pos = 0 + else: + # add new tasks after group title + to_pos = 1 + + tasks = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:] + + # Move tasks + path = generate_group_path(group) + for n,t in enumerate(tasks): + if n == to_pos: + # set orderid on top + order_id = baseconvert(n) + else: + os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:])) + + else: + # start at 0 when group is empty + order_id = baseconvert(0) + tid_in_groups_path = '%s/%s%s'%(generate_group_path(group),order_id,tid) + # remove double slash that can come up + tid_in_groups_path = tid_in_groups_path.replace('//','/') + f = open(tid_in_groups_path,'w') + f.close() + + # return tid_in_groups_path to easily add new tasks to group_directory_file_list and save time + return tid_in_groups_path + +## creates a task and opens vi +# @param[in] group task id +# @ingroup EDI_CORE +def create_task(group): + # Open text editor + # create task in tasks folder +#:define create_task_part1 + tid = generate_id() + + # Save text in tasks + task_path = generate_task_path(tid) + os.mkdir(task_path) +#:end + os.system('%s %s/description.txt' % (editor,task_path)) + if not os.path.isfile('%s/description.txt' % task_path): + # file doesnt exist, abort task creation + shutil.rmtree(task_path) + return 'no new task, the description is empty' + + # Save creation time +#:define save_task_creation_time + f = open('%s/ctime.txt'%task_path,'w') + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path) + task_mtime = time.ctime(mtime) + f.write(task_mtime) + f.close() +#:end + + # create status, active by default +#:define create_task_part2 + # Create status + f = open('%s/status'%task_path,'w') + f.write(TASK_STATUS[TASK_STATUS_ACTIVE]) + f.close() + + tid_in_groups_path = add_task_to_group_folder(tid, group) + + global group_directory_file_list + if group_directory_file_list: + group_directory_file_list.append(tid_in_groups_path) + + # autolink + global autolink + if autolink: + for g in autolink: + if is_this_task_a_group(g): + # link to groups existing in current database + add_task_reference_to_a_group(tid,g) +#:end + edi_log('created %s in group %s'%(tid,group)) + return tid + +## create task and copy text file +# @param[in] group task id +# @param[in] text_file filename of text file to copy +# @ingroup EDI_CORE +def add_task(group,text_file): +#:create_task_part1 + tid = generate_id() + + # Save text in tasks + task_path = generate_task_path(tid) + os.mkdir(task_path) + shutil.copy(text_file,'%s/description.txt'%task_path) +#:save_task_creation_time + f = open('%s/ctime.txt'%task_path,'w') + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path) + task_mtime = time.ctime(mtime) + f.write(task_mtime) + f.close() +#:create_task_part2 + # Create status + f = open('%s/status'%task_path,'w') + f.write(TASK_STATUS[TASK_STATUS_ACTIVE]) + f.close() + + tid_in_groups_path = add_task_to_group_folder(tid, group) + + global group_directory_file_list + if group_directory_file_list: + group_directory_file_list.append(tid_in_groups_path) + + # autolink + global autolink + if autolink: + for g in autolink: + if is_this_task_a_group(g): + # link to groups existing in current database + add_task_reference_to_a_group(tid,g) + edi_log('created %s in group %s'%(tid,group)) + return tid + +## create task with description text +# @param[in] group task id +# @param[in] text string +# @ingroup EDI_CORE +def add_text(group,text): +#:create_task_part1 + tid = generate_id() + + # Save text in tasks + task_path = generate_task_path(tid) + os.mkdir(task_path) + f = open('%s/description.txt'%task_path,'w') + f.write(text) + f.close() +#:save_task_creation_time + f = open('%s/ctime.txt'%task_path,'w') + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path) + task_mtime = time.ctime(mtime) + f.write(task_mtime) + f.close() +#:create_task_part2 + # Create status + f = open('%s/status'%task_path,'w') + f.write(TASK_STATUS[TASK_STATUS_ACTIVE]) + f.close() + + tid_in_groups_path = add_task_to_group_folder(tid, group) + + global group_directory_file_list + if group_directory_file_list: + group_directory_file_list.append(tid_in_groups_path) + + # autolink + global autolink + if autolink: + for g in autolink: + if is_this_task_a_group(g): + # link to groups existing in current database + add_task_reference_to_a_group(tid,g) + edi_log('created %s in group %s'%(tid,group)) + return tid + +## create task and copy text file, filename is task title +# @param[in] group task id +# @param[in] text_file filename of text file to copy +# @ingroup EDI_CORE +def add_task_and_filename(group,text_file): + tid = add_task(group,text_file) + + # Add file name on first line + f = open('%s/description.txt'%generate_task_path(tid)) + des = f.readlines() + f.close() + f = open('%s/description.txt'%generate_task_path(tid),'w') + # save text filename only + f.write('%s\n'%text_file.split('/')[-1]) + for l in des: + f.write(l) + f.close() + + return tid + +## create many tasks from a text file +# @param[in] group task id +# @param[in] text_file filename of text file to copy +# @ingroup EDI_CORE +def add_many_tasks(group,text_file): + r = '' + + # Copy a task to text string + # Remove '#' from first line, first character position + # Add task to database + f = open(text_file) + status = 'start' + text = '' + number_of_tasks = 0 + for l in f.readlines(): + if l.rstrip() == '---': + if status != 'start': + # add task to database + add_text(group,text) + number_of_tasks += 1 + text = '' + status = 'start' + else: + if (status == 'start') and (l[0] == '#'): + l = l[1:] + text += l + status = 'writing task' + f.close() + if status == 'writing task': + # add task to database + add_text(group,text) + number_of_tasks += 1 + + edi_log('created %s tasks in group %s'%(number_of_tasks,group)) + r = 'created %s tasks in group %s'%(number_of_tasks,group) + return r + +## create many one line tasks from a text file +# @param[in] group task id +# @param[in] text_file filename of text file to copy +# @ingroup EDI_CORE +def add_many_one_line_tasks(group,text_file): + r = [] + + f = open(text_file) + number_of_tasks = 0 + for l in f.readlines(): + add_text(group,l.strip()) + number_of_tasks += 1 + f.close() + + edi_log('created %s tasks in group %s'%(number_of_tasks,group)) + r = 'created %s tasks in group %s'%(number_of_tasks,group) + return r + +## create group from text file +# @param[in] group task id +# @param[in] text_file with group structure +# @ingroup EDI_CORE +def add_many_groups_from_text(group,text_file): + r = [] + + f = open(text_file) + number_of_tasks = 0 + group_stack = [group] + for l in f.readlines(): + # count space indents + l_l = l.split(' ') + indent = 0 + for s in l_l: + if s: + break + indent += 1 + if indent < len(group_stack)-1: + # remove groups from stack if same level or higher levels + del group_stack[-(len(group_stack)-1 - indent):] + l = l.strip() + tid = add_text(group_stack[-1],l) + create_group(tid) + group_stack.append(tid) + number_of_tasks += 1 + f.close() + + edi_log('created %s groups in group %s'%(number_of_tasks,group)) + r = 'created %s groups in group %s'%(number_of_tasks,group) + return r + +## copy description to path using first line of description as filename +# @return path and filname +# @param[in] tid task id +# @param[in] path destination directory for task description +# @ingroup EDI_CORE +def export_task_to_a_file(tid,path): + f = open('%s/description.txt'%generate_task_path(tid)) + fn = f.readline().strip() + des = f.readlines() + f.close() + f = open('%s/%s'%(path,fn),'w') + for l in des: + f.write(l) + f.close() + r = '%s/%s'%(path,fn) + # remove eventual double // + return r.replace(os.sep*2,os.sep) + +## print description of task tid +# @return list of strings +# @param[in] tid task id +# @ingroup EDI_CORE +def display_task(tid): + task_path = generate_task_path(tid) + f = open('%s/description.txt'%task_path) + description = f.readlines() + f.close() + + # print tid, status and first line + description[0] = generate_task_string_with_tid(tid,description[0]) + return description + +## find group containing task tid in groups folder +# @return tid +# @param[in] tid task id +# @ingroup EDI_CORE +def find_group_containing_task(tid): + if tid == 'root': + # root has not parent group, return root + return tid +#:define walk_groups + global data_location_groups + # reuse previously created group list, to save time + global group_directory_file_list + if not group_directory_file_list: + group_directory_file_list = ffind(data_location_groups) + groups_and_tasks = group_directory_file_list + for t in groups_and_tasks: + if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]): + group = t.split('/')[-2] +#:end + try: + a = group + except: + print '\n\nEDI_ERROR: Database inconsistent - run: find %s|grep %s and remove reference.\n'%(data_location, tid) + group = 'error' + return group + +## find group containing task tid in groups folder and print +# @return list of strings +# @param[in] tid task id +# @ingroup EDI_CORE +# Shows path in tree and title for each group in path +def show_group_for_task(tid): + global data_location_tree + r = [] + + # set group string to be displayed on first line + group_s = ' ' + if is_this_task_a_group(tid): + group_s = 'GROUP' + if is_linked(tid): + group_s = ' LINK' + +#:walk_groups + global data_location_groups + # reuse previously created group list, to save time + global group_directory_file_list + if not group_directory_file_list: + group_directory_file_list = ffind(data_location_groups) + groups_and_tasks = group_directory_file_list + for t in groups_and_tasks: + if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]): + group = t.split('/')[-2] + tree_path = find_group_in_tree(group) + # p is data_location folder/tree + p_l = data_location_tree.split('/')[-2:] + p = '/'.join(p_l) + # if empty then command is run in tree root + if tree_path.split(p)[-1]: + # print path of tids: tid/tid... + r.append(tree_path.split(p)[-1][1:]) + group_titles_in_path = [] + for g in tree_path.split(p)[-1][1:].split('/'): + group_titles_in_path.append(get_task_title(g)) + # print title/title... + r.append('/'.join(group_titles_in_path)) + r.append(generate_group_string_with_tid(group,get_task_title(group))) + r.append('') + + # Print media and attachments + # media type + media = get_media(tid)[0] + # attachment list + attachments = get_attachments(tid) + + # Print task, colors, group list + color = get_forground_color(tid) + fc = '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3]) + color = get_background_color(tid) + bc = '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3]) + # Figure out creation time and modification times for description and status + task_ctime = get_creation_date(tid)[0] + + task_path = generate_task_path(tid) + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path) + description_mtime = time.ctime(mtime) + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path) + status_mtime = time.ctime(mtime) + r = ['Task:\n%s\n\n'%generate_task_string_with_tid( tid,'%s %s'%(group_s, get_task_title(tid)) )] + ['Media type: %s'%media] + ['\nAttachments:'] + attachments + ['\n\nforeground color: %s\nbackground color: %s\nCreation time: %s\nLast description change: %s\nLast status change: %s\n\nGroup list:\n' % (fc, bc, task_ctime, description_mtime, status_mtime)] + r + return r + +## find group in tree folder +# @return path_in_tree +# @param[in] tid task id +# @ingroup EDI_CORE +def find_group_in_tree(group): + if group == 'root': + path_in_tree = data_location_tree + else: + path_in_tree = '' + # list all group paths in tree + f = os.popen('find %s'%data_location_tree) + groups = [i.strip() for i in f.readlines()] + f.close() + # remove data_location_tree from paths + del groups[0] + # find the group in group paths + for g in groups: + if g.split('/')[-1] == group: + path_in_tree = g + return path_in_tree + +## Determines if a task has multiple references +# @return integer 0 or 1 +# @param[in] tid task id +# @ingroup EDI_CORE +def is_linked(tid): + status = 0 + task_linked_groups_path = '%s/groups/' % generate_task_path(tid) + # check if tid/groups exists + if os.path.exists(task_linked_groups_path): + groups = os.listdir(task_linked_groups_path) + # check if task is linked to more than 1 group + if len(groups) > 1: + status = 1 + return status + +## convert task to group +# @return list of stings when there is an error +# @param[in] tid task id +# @ingroup EDI_CORE +def create_group(tid): + r = [] + # convert task to group only when task is not linked + if is_linked(tid): + r.append('Converting linked task to group removes links. The task groups are:') + r += show_group_for_task(tid) + + # delete tid/groups because groups are not linked + task_linked_groups_path = '%s/groups/' % generate_task_path(tid) + groups = os.listdir(task_linked_groups_path) + # remove all links except for the first group + for g in groups[1:]: + delete_linked_task(g,tid) + if os.path.exists(task_linked_groups_path): + shutil.rmtree(task_linked_groups_path) + r.append('Created group %s in %s'%(tid,groups[0])) + # create new group in groups folder + os.mkdir('%s/%s'%(data_location_groups,tid)) + # First task in group is group title task + order_id = baseconvert(0) + f = open('%s/%s%s'%(generate_group_path(tid),order_id,tid),'w') + f.close() + + # Add group in tree + # update group list + global group_directory_file_list + group_directory_file_list = [] + group = find_group_containing_task(tid) + if group == 'root': + os.mkdir('%s/%s'%(data_location_tree,tid)) + else: + os.mkdir('%s/%s'%(find_group_in_tree(group),tid)) + + # Change status active to void by default + # To avoid filtering groups + if get_status(tid) == TASK_STATUS[TASK_STATUS_ACTIVE]: + # set status to void + set_status(tid,TASK_STATUS_VOID) + edi_log('created group %s'%tid) + return r + +## convert group to task +# @param[in] group group to convert to task +# @ingroup EDI_CORE +def convert_group_to_task(group): + r = [] + + # list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder + groups_and_tasks = ffind(data_location_groups) + + convert_group_status = 'delete' + for t2 in groups_and_tasks: + # search a task in group that is not tid + if ('%s/'%group in t2) and (group not in t2.split('/')[-1]): + convert_group_status = 'There is another task in the group, keep group.' + r.append(convert_group_status) + if (convert_group_status == 'delete') and (not 'root' in group): + # Delete group title and group folder + os.remove('%s/%s%s'%(generate_group_path(group),baseconvert(0),group)) + os.rmdir(generate_group_path(group)) + # Delete group in tree + if find_group_in_tree(group): + shutil.rmtree(find_group_in_tree(group)) + # change state from void to active + set_status(group,TASK_STATUS_ACTIVE) + edi_log('converted group %s to task'%group) + return r + +## delete task tid in all groups +# @return group id +# @param[in] tid task id +# @ingroup EDI_CORE +def delete_task(tid): + # save original tid parameter for log + tid_param = tid + # Delete task in tasks + shutil.rmtree(generate_task_path(tid)) + + # Delete task reference in groups + # list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder + groups_and_tasks = ffind(data_location_groups) + + # Identify if task is a group + is_a_group = is_this_task_a_group(tid) + + # Return group task, it changes when first task is deleted + group_id = '' + # list groups to reorder at the end + reorder_groups = [] + for t in groups_and_tasks: + # Delete reference in upper group, not the title task of the group + if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]): + # group is the group for task tid + group = t.split('/')[-2] + group_id = group + # Delete task reference in group + os.remove(t) + reorder_groups.append(group_id) + if is_a_group: + # First task becomes a group, add a reference in group group + # list tasks in order in tid group + group_tasks = sorted(os.listdir(generate_group_path(tid))) + # Delete emtpy group or first task in group becomes the group title. + if len(group_tasks) == 1: + shutil.rmtree(generate_group_path(tid)) + # Delete group in tree + # when a group is deleted, subgroups are automatically deleted in the tree + if find_group_in_tree(tid): + shutil.rmtree(find_group_in_tree(tid)) + else: + # Remove order_id, keep task id only + first_task = group_tasks[1][ORDER_ID_LENGTH:] + group_id = first_task + # Create an entry in group at the same position as tid had + order_id = t.split('/')[-1][:ORDER_ID_LENGTH] + f = open('%s/%s%s'%(generate_group_path(group),order_id,first_task),'w') + f.close() + + # Delete group task of group of more then 2 tasks, first task becomes a group + # delete orderidtaskid + os.remove('%s/%s'%(generate_group_path(tid),group_tasks[0])) + os.rename('%s/%s'%(generate_group_path(tid),group_tasks[1]),'%s/%s%s'%(generate_group_path(tid),baseconvert(0),first_task)) + # reorder tasks to remove gap between group title task and first task + path = generate_group_path(tid) + tasks = sorted(os.listdir(path)) + for n,t in enumerate(tasks): + os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:])) + # rename group in groups folder + os.rename(generate_group_path(tid),generate_group_path(first_task)) + # rename group in tree folder + group_tree_path = find_group_in_tree(tid) + group_tree_path_l = group_tree_path.split('/') + group_tree_path_l[-1] = first_task + new_group_tree_path = '/'.join(group_tree_path_l) + os.rename(group_tree_path,new_group_tree_path) + + # reorder tasks to remove gaps in reorder_groups + for tid in reorder_groups: + path = generate_group_path(tid) + tasks = sorted(os.listdir(path)) + for n,t in enumerate(tasks): + os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:])) + + # Return group task + edi_log('deleted %s in %s'%(tid_param,' '.join(reorder_groups))) + return group_id + +## Delete task only if it is linked in one group +# @return group id +# @param[in] group task id +# @param[in] tid task id +# @ingroup EDI_CORE +def delete_linked_task(group,tid): + group_id = group + if not is_linked(tid): + group_id = delete_task(tid) + else: + # Delete task reference in group + # find task in group: ORDER_IDTASK_ID + tasks = os.listdir(generate_group_path(group)) + for t in tasks: + if t[ORDER_ID_LENGTH:] == tid: + os.remove('%s%s' % (generate_group_path(group), t)) + + # delete group in tid/groups + os.remove('%s/groups/%s' % (generate_task_path(tid), group)) + + # reorder tasks to remove gaps in group + path = generate_group_path(group) + tasks = os.listdir(path) + for n,t in enumerate(tasks): + os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:])) + + edi_log('deleted %s in group %s'%(tid,group)) + + return group_id + +## delete group tid +# @return group id +# @param[in] tid task id +# @ingroup EDI_CORE +def delete_group(tid): + # Delete tasks in group + group_tasks = sorted(os.listdir(generate_group_path(tid))) + if (len(group_tasks) == 0) and (tid == 'root'): + # the database is already empty + return tid + if tid != 'root': + # root group doesnt have a title + # Remove group title from the loop to delete tasks only and then group + del group_tasks[0] + + # Delete tasks and groups recursively + for t in group_tasks: + # Remove order_id, keep task id only + oid = t[ORDER_ID_LENGTH:] + if not is_this_task_a_group(oid): + # delete tasks that are linked only once + delete_linked_task(tid, oid) + else: + delete_group(oid) + + if tid != 'root': + # never delete root group + return delete_task(tid) + else: + # return root and keep root task + return tid + +## edit task with vi +# @param[in] tid task id +# @ingroup EDI_CORE +def edit_task(tid): + os.system('%s %s/description.txt' % (editor, generate_task_path(tid))) + edi_log('edited %s'%tid) + +## move task from group at at_pos to to_pos and reorder +# @return list of stings when there is an error +# @param[in] group task id +# @param[in] at_pos selected position +# @param[in] to_pos insert position +# @ingroup EDI_CORE +def change_task_order(group,at_pos,to_pos): + # save original group parameter for log + group_param = group + # List tasks in group + r = [] + path = generate_group_path(group) + tasks = sorted(os.listdir(path)) + + # Verify position + # Get task + try: + orderid_and_tid = tasks[at_pos] + except: + r.append('%d is an invalid position.'%at_pos) + return r + if to_pos == 0: + tid = orderid_and_tid[ORDER_ID_LENGTH:] + # do not move linked tasks to position 0 + if is_linked(tid): + r.append('Converting linked task to group removes links. The task groups are:') + r += show_group_for_task(tid) + + # delete tid/groups because groups are not linked + task_linked_groups_path = '%s/groups/' % generate_task_path(tid) + groups = os.listdir(task_linked_groups_path) + # remove all links except for the first group + for g in groups: + if not g == group: + delete_linked_task(g,tid) + if os.path.exists(task_linked_groups_path): + shutil.rmtree(task_linked_groups_path) + parent_group = find_group_containing_task(group) + r.append('Created group %s in %s'%(tid,parent_group)) + # do not move groups to position 0 + if is_this_task_a_group(tid): + r.append('Having a group in group title is not supported.') + return r + # Insert task at to_pos + if to_pos > at_pos: + # +1 because at_pos reference will be deleted and will shift to_pos reference + to_pos += 1 + tasks = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:] + # Delete task at at_pos + del tasks[at_pos] + else: + # rename group title, when to_pos in 0 + if (to_pos == 0) and (not group == 'root'): + to_pos_tid = tasks[to_pos][ORDER_ID_LENGTH:] + + tasks = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:] + # Delete task at at_pos+1 because the new position is before at_pos + # at_pos was shifted when to_pos reference was added above + del tasks[at_pos+1] + +#:define reorder_tasks + # Move tasks + for n,t in enumerate(tasks): + os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:])) +#:end + + # rename group title, when to_pos in 0 + if (to_pos == 0) and (not group == 'root'): + # rename group in parent group + # group is tid for parent group and to_pos_tid is equal to group input parameter because to_pos is 0 + group = find_group_containing_task(group) + # to_pos_orderid_and_tid is group in parent group + parent_group_tasks = os.listdir(generate_group_path(group)) + for t in parent_group_tasks: + if t[ORDER_ID_LENGTH:] == to_pos_tid: + to_pos_orderid_and_tid = t + # remove order_id, keep task id only for new group + group_id = orderid_and_tid[ORDER_ID_LENGTH:] + # create an entry in parent group at the same position as to_pos tid had + order_id = to_pos_orderid_and_tid[:ORDER_ID_LENGTH] + f = open('%s/%s%s'%(generate_group_path(group),order_id,group_id),'w') + f.close() + + # delete group task of group + # delete orderidtaskid + os.remove('%s/%s'%(generate_group_path(group),to_pos_orderid_and_tid)) + + # rename group in groups folder + os.rename(generate_group_path(to_pos_tid),generate_group_path(group_id)) + # rename group in tree folder + group_tree_path = find_group_in_tree(to_pos_tid) + group_tree_path_l = group_tree_path.split('/') + group_tree_path_l[-1] = group_id + new_group_tree_path = '/'.join(group_tree_path_l) + os.rename(group_tree_path,new_group_tree_path) + + # rename group in linked task and former linked task with a 'groups' folder in tasks database folder + group_tasks = os.listdir(generate_group_path(group_id)) + for group_task in group_tasks: + if os.path.exists('%s/groups' % generate_task_path(group_task[ORDER_ID_LENGTH:])): + # search for to_pos_tid (group input parameter) in task path groups folder + link_groups = os.listdir('%s/groups' % generate_task_path(group_task[ORDER_ID_LENGTH:])) + for lg in link_groups: + if to_pos_tid == lg: + # rename group to new group id since the title changed + os.rename('%s/groups/%s' % (generate_task_path(group_task[ORDER_ID_LENGTH:]),to_pos_tid), '%s/groups/%s' % (generate_task_path(group_task[ORDER_ID_LENGTH:]),group_id)) + + edi_log('changed task order %s to %s in group %s'%(at_pos, to_pos, group_param)) + return r + + +## move task to group +# @return tid +# @param[in] tgroup task id, select a task in this group +# @param[in] tid task id +# @param[in] group task id, destination group +# @ingroup EDI_CORE +def move_task_to_a_group(tgroup,tid,group): + + # find task tid in tgroup and remove task + path = generate_group_path(tgroup) + tasks = sorted(os.listdir(path)) + task_in_group = '' + for n,t in enumerate(tasks): + # remove task from tasks to reorder tgroup + if t[ORDER_ID_LENGTH:] == tid: + task_in_group = t + del tasks[n] + break + if not task_in_group: + return '%s not found in %s'%(tid,tgroup) + + # Move group in tree + if is_this_task_a_group(tid): + if find_group_in_tree(tid)[:-ID_LENGTH-1] == find_group_in_tree(group): + # prevent moving a group on itself, in tree and moving group title position to parent group + return tid + shutil.move(find_group_in_tree(tid),find_group_in_tree(group)) + + # Remove task in source group + os.remove('%s/%s'%(path,task_in_group)) + +#:reorder_tasks + # Move tasks + for n,t in enumerate(tasks): + os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:])) + # check that tid is not already linked in destination group + if is_linked(tid): + link_groups = os.listdir('%s/groups/' % generate_task_path(tid)) + if group in link_groups: + # removed task reference from tgroup. Remove tgroup reference in task. There is already a tid reference in group, nothing more to do + os.remove('%s/groups/%s' % (generate_task_path(tid), tgroup)) + return tid + + # Create reference in destination group + add_task_to_group_folder(tid, group) + + # linked tasks, remove source group and add destination group + if is_linked(tid): + # remove source tgroup from tid/groups + os.remove('%s/groups/%s' % (generate_task_path(tid), tgroup)) + f = open('%s/groups/%s'%(generate_task_path(tid), group),'w') + f.close() + + edi_log('moved %s in group %s to group %s'%(tid,tgroup,group)) + return tid + +## copy task to group with new tid +# @return new tid +# @param[in] tid task id +# @param[in] group task id, destination group +# @ingroup EDI_CORE +def copy_task_to_a_group(tid,group): + # Generate new tid + newtid = generate_id() + # Copy task in tasks + shutil.copytree(generate_task_path(tid),generate_task_path(newtid)) + # delete tid/groups because new task is not linked + task_linked_groups_path = '%s/groups/' % generate_task_path(newtid) + if os.path.exists(task_linked_groups_path): + shutil.rmtree(task_linked_groups_path) + # Copy group in groups and in tree + if is_this_task_a_group(tid): + # create new group + shutil.copytree(generate_group_path(tid),generate_group_path(newtid)) + # Change group title task to newtid + shutil.move('%s/%s%s'%(generate_group_path(newtid),baseconvert(0),tid),'%s/%s%s'%(generate_group_path(newtid),baseconvert(0),newtid)) + + # delete tasks to be recreated with new tid + tasks = sorted(os.listdir(generate_group_path(newtid))) + del tasks[0] + for t in tasks: + os.remove('%s/%s'%(generate_group_path(newtid),t)) + + # Add group in tree + if group == 'root': + os.mkdir('%s/%s'%(data_location_tree,newtid)) + else: + os.mkdir('%s/%s'%(find_group_in_tree(group),newtid)) + + + # Add reference in group + tmp_tid = tid + tid = newtid + # Create reference in destination group + add_task_to_group_folder(tid, group) + tid = tmp_tid + + if is_this_task_a_group(tid): + # walk in group + # list items in group + tasks = sorted(os.listdir(generate_group_path(tid))) + + # add group found in first group + for t in tasks: + # Check tasks that are not title task in a group + if t[ORDER_ID_LENGTH:] != tid: + # copy_task_to_a_group recursively + copy_task_to_a_group(t[ORDER_ID_LENGTH:],newtid) + edi_log('copied %s to group %s, created %s'%(tid,group,newtid)) + return newtid + +## create task path in database using database name +# @return path to task in tasks +# @param[in] tid task id +# @param[in] location database name in data section of easydoneit.ini +# @ingroup EDI_CORE +def generate_task_path_in_database(tid,location): + global selected + global selected_path + + z = dict(zip(selected, selected_path)) + location_tasks = '%s/tasks'%z[location] + return '%s/%s'%(location_tasks,tid) + +## create group path in database using database name +# @return path to group in groups +# @param[in] tid task id +# @param[in] location database name in data section of easydoneit.ini +# @ingroup EDI_CORE +def generate_group_path_in_database(tid,location): + global selected + global selected_path + + z = dict(zip(selected, selected_path)) + location_groups = '%s/groups'%z[location] + return '%s/%s/'%(location_groups,tid) + +## find group in tree folder in database +# @return path to group in tree +# @param[in] group task id +# @param[in] location database name in data section of easydoneit.ini +# @ingroup EDI_CORE +def find_group_in_tree_in_database(group,location): + global selected + global selected_path + + z = dict(zip(selected, selected_path)) + location_tree = '%s/tree'%z[location] + + if group == 'root': + path_in_tree = location_tree + else: + path_in_tree = '' + # list all group paths in tree + f = os.popen('find %s'%location_tree) + groups = [i.strip() for i in f.readlines()] + f.close() + # remove data_location_tree from paths + del groups[0] + # find the group in group paths + for g in groups: + if g.split('/')[-1] == group: + path_in_tree = g + return path_in_tree + +## add task with new tid in selected database +# @param[in] tid task id +# @param[in] group task id +# @param[in] location database name in data section of easydoneit.ini +# @ingroup EDI_CORE +def add_task_to_group_folder_in_database(tid,group,location): + # Create an entry in group + tasks = sorted(os.listdir(generate_group_path_in_database(group,location))) + # Add +1 to last order_id to have the task last in the list + if tasks: + order_id = baseconvert(baseconvert_to_dec(tasks[-1][:ORDER_ID_LENGTH])+1) + else: + # start at 0 when group is empty + order_id = baseconvert(0) + f = open('%s/%s%s'%(generate_group_path_in_database(group,location),order_id,tid),'w') + f.close() + +## copy task to group in selected database with new tid +# @return new tid +# @param[in] tid task id +# @param[in] group task id +# @param[in] location database name in data section of easydoneit.ini +# @ingroup EDI_CORE +def copy_task_to_database(tid,location,group): + # Generate new tid + newtid = generate_id() + # Copy task in tasks + shutil.copytree(generate_task_path(tid),generate_task_path_in_database(newtid,location)) + # delete tid/groups because new task is not linked + task_linked_groups_path = '%s/groups/' % generate_task_path_in_database(newtid,location) + if os.path.exists(task_linked_groups_path): + shutil.rmtree(task_linked_groups_path) + # Copy group in groups and in tree + if is_this_task_a_group(tid): + # create new group + shutil.copytree(generate_group_path(tid),generate_group_path_in_database(newtid,location)) + # Change group title task to newtid + shutil.move('%s/%s%s'%(generate_group_path_in_database(newtid,location),baseconvert(0),tid),'%s/%s%s'%(generate_group_path_in_database(newtid,location),baseconvert(0),newtid)) + + # delete tasks to be recreated with new tid + tasks = sorted(os.listdir(generate_group_path_in_database(newtid,location))) + del tasks[0] + for t in tasks: + os.remove('%s/%s'%(generate_group_path_in_database(newtid,location),t)) + + # Add group in tree + global selected + global selected_path + + z = dict(zip(selected, selected_path)) + location_tree = '%s/tree'%z[location] + if group == 'root': + os.mkdir('%s/%s'%(location_tree,newtid)) + else: + os.mkdir('%s/%s'%(find_group_in_tree_in_database(group,location),newtid)) + + + # Add reference in group + # Create reference in destination group + add_task_to_group_folder_in_database(newtid, group, location) + + if is_this_task_a_group(tid): + # walk in group + # list items in group + tasks = sorted(os.listdir(generate_group_path(tid))) + + # add group found in first group + for t in tasks: + # Check tasks that are not title task in a group + if t[ORDER_ID_LENGTH:] != tid: + # copy_task_to_database recursively + copy_task_to_database(t[ORDER_ID_LENGTH:],location,newtid) + return newtid + +## move task to database/group +# @param[in] tgroup group id for task tid +# @param[in] tid task id +# @param[in] location database name in data section of easydoneit.ini +# @param[in] group destination group id +# @ingroup EDI_CORE +# copy and delete +def move_task_to_a_group_to_database(tgroup,tid,location,group): + newtid = copy_task_to_database(tid,location,group) + if is_this_task_a_group(tid): + delete_group(tid) + else: + delete_linked_task(tgroup,tid) + return newtid + +## add reference to tid in group +# @param[in] tid task id +# @param[in] group destination group id +# @ingroup EDI_CORE +# Used in edi ln to link tasks +def add_task_reference_to_a_group(tid,group): + prints = [] + if is_this_task_a_group(tid): + prints.append('Select a task to link instead of a group') + else: + # add group to task folder + task_path = generate_task_path(tid) + if not os.path.exists('%s/groups/' % task_path): + os.mkdir('%s/groups/' % task_path) + + # add first group when task is linked to tid/groups/ + first_group = find_group_containing_task(tid) + f = open('%s/groups/%s'%(task_path,first_group),'w') + f.close() + f = open('%s/groups/%s'%(task_path,group),'w') + f.close() + + # add task to group + add_task_to_group_folder(tid, group) + + edi_log('linked %s to group %s'%(tid,group)) + return prints + +## set status for tid +# @param[in] tid task id +# @param[in] status_number index in edi_core.TASK_STATUS +# @ingroup EDI_CORE +def set_status(tid,status_number): + # Change status + f = open('%s/status'%generate_task_path(tid),'w') + f.write(TASK_STATUS[status_number]) + f.close() + edi_log('set status for %s to %s'%(tid,TASK_STATUS[status_number].strip())) + +## set all tasks in group to active +# @param[in] tid task id +# @ingroup EDI_CORE +def reset_group_status(tid): + group_tasks = os.listdir(generate_group_path(tid)) + for t in group_tasks: + if (not is_this_task_a_group(t[ORDER_ID_LENGTH:])) or (is_this_task_a_group(t[ORDER_ID_LENGTH:]) and (get_status(t[ORDER_ID_LENGTH:]) != TASK_STATUS[TASK_STATUS_VOID])): + set_status(t[ORDER_ID_LENGTH:],TASK_STATUS_ACTIVE) + edi_log('reset group %s'%tid) + +## search string in tasks folder +# @param[in] search query string +# @ingroup EDI_CORE +def search_string(search): + global data_location_tasks + global status_filters_d + global STATUS_FILTER_STATES + + tasks = sorted(os.listdir(data_location_tasks)) + + # search in descriptions only + all_grep_r = [] + for tid in tasks: + if tid != 'root': + # search in visible tasks only (filter enable status) + task_status = get_status(tid) + if status_filters_d[task_status] == STATUS_FILTER_STATES[0]: + f = os.popen('grep -i -R "%s" %s/description.txt'%(search,generate_task_path(tid))) + # add tid and filename to results + grep_r = ['/%s/description.txt:%s'%(tid,i) for i in f.readlines()] + f.close() + all_grep_r += grep_r + + grep_r = all_grep_r + + # r lists all hits + r = [] + for l in grep_r: + # replace filename with tid and title (first line in file) + # read first line in file + l_l = l.split(':') + hit_filename = l_l[0] + f = open('%s%s'%(data_location_tasks,hit_filename)) + title = f.readline().strip() + f.close() + # set tid + tid = hit_filename.split('/')[1] + group = ' ' + if is_this_task_a_group(tid): + group = 'GROUP' + if is_linked(tid): + group = ' LINK' + l_l[0] = '%s %s - %s'%(tid,group,title) + l = ':'.join(l_l) + r.append(l) + + return r + +## search string in group +# @param[in] group task id +# @param[in] search query string +# @ingroup EDI_CORE +def search_string_in_group(group,search): + + tasks = sorted(os.listdir(generate_group_path(group))) + + # r lists all hits + r = [] + for orderidtid in tasks: + # tid for current task in group + tid = orderidtid[ORDER_ID_LENGTH:] + # search in visible tasks only (filter enable status) + task_status = get_status(tid) + grep_r = [] + if status_filters_d[task_status] == STATUS_FILTER_STATES[0]: + f = os.popen('grep -i -R "%s" %s/description.txt'%(search,generate_task_path(tid))) + # add tid and filename to results + grep_r = ['/%s/description.txt:%s'%(tid,i) for i in f.readlines()] + f.close() + + for l in grep_r: + # replace filename with tid and title (first line in file) + # read first line in file + l_l = l.split(':') + hit_filename = l_l[0] + f = open('%s%s'%(data_location_tasks,hit_filename)) + title = f.readline().strip() + f.close() + # set tid + tid = hit_filename.split('/')[1] + group = ' ' + if is_this_task_a_group(tid): + group = 'GROUP' + if is_linked(tid): + group = ' LINK' + l_l[0] = '%s %s - %s'%(tid,group,title) + l = ':'.join(l_l) + r.append(l) + + return r + +## search string in tree +# @param[in] group task id +# @param[in] search query string +# @ingroup EDI_CORE +def search_string_in_tree(group,search): + # walk_group is the list of groups to visit. FIFO + r = [] + walk_group = [group] + # the while loop goes through all the group that are found + while walk_group: + # list items in first group + tasks = sorted(os.listdir(generate_group_path(walk_group[0]))) + r += search_string_in_group(walk_group[0],search) + + # add group found in first group + for t in tasks: + # Check tasks that are not title task in a group + if (t[ORDER_ID_LENGTH:] != walk_group[0]) and is_this_task_a_group(t[ORDER_ID_LENGTH:]): + walk_group.append(t[ORDER_ID_LENGTH:]) + + # remove first group to list items in next group + del walk_group[0] + return r + +## show tree +# @ingroup EDI_CORE +# print all trees: group tids and titles +def show_tree(): + r = [] + f = os.popen('cd %s;find .'%data_location_tree) + # remove first empty line + tidtree = f.readlines() [1:] + f.close() + + # remove './' from path + tidtree = [i[2:] for i in tidtree] + + # print titles path\n group tid path\n\n + for l in tidtree: + # find title for group tids + group_titles_in_path = [] + for g in l.strip().split(os.sep): + group_titles_in_path.append(get_task_title(g)) + if user_interface == 'web': + # convert / to - to be able to create links correctly + group_titles_in_path = [i.replace('/', '-') for i in group_titles_in_path] + # create string title/title... + group_titles_in_path_s = '/'.join(group_titles_in_path) + r.append('%s\n'%group_titles_in_path_s) + r.append('%s\n'%l) + + return r + +## group statistics +# @ingroup EDI_CORE +# print statistics for a group recursively +def group_statistics(group): + global stats + global stats_total + global stats_creation_dates + global stats_overtime + + # compute total number of tasks in group + path = generate_group_path(group) + + tasks = [] + # remove group title + for i in os.listdir(path): + if not baseconvert(0) in i[:ORDER_ID_LENGTH]: + tasks.append(i[ORDER_ID_LENGTH:]) + + stats_total += len(tasks) + + # compute number of tasks in each state, groups and links + for tid in tasks: + task_status = get_status(tid) + stats[task_status] += 1 + if is_this_task_a_group(tid): + stats[' Group'] += 1 + group_statistics(tid) + if is_linked(tid): + stats[' Linked'] += 1 + + #stats_creation_dates + # Figure out creation time and modification time for description + task_ctime = get_creation_date(tid)[1] + + task_path = generate_task_path(tid) + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path) + # 'if' below to be compatible with first database format + if task_ctime == 0: + # ctime not available + task_ctime = mtime + stats_creation_dates.append(task_ctime) + + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path) + + cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime)) + sdate = time.strftime("%Y-%m-%d", time.localtime(mtime)) + if not stats_overtime.has_key(cdate): + # initialize date dict + stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) + stats_overtime[cdate]['Creation'] += 1 + if not stats_overtime.has_key(sdate): + # initialize date dict + stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) + stats_overtime[sdate][task_status] += 1 + #end + +## statistics +# @ingroup EDI_CORE +# print statistics for a group or a database +# compute speed +def statistics(group): + global stats + global stats_total + global stats_creation_dates + global stats_overtime + r = [] + + # initialize stats dictionary + stat_keys = [TASK_STATUS[i] for i in range(len(TASK_STATUS))] + stat_keys.append(' Group') + stat_keys.append(' Linked') + state_amounts = [0 for i in range(len(stat_keys))] + stats = dict(zip(stat_keys,state_amounts)) + + if group == 'for database': + # compute total number of tasks, excluding root + path = data_location_tasks + tasks = [] + for i in os.listdir(path): + if i != 'root': + tasks.append(i) + + # compute number of tasks in each state, groups and links + for tid in tasks: + task_status = get_status(tid) + stats[task_status] += 1 + if is_this_task_a_group(tid): + stats[' Group'] += 1 + if is_linked(tid): + stats[' Linked'] += 1 + +#:define stats_creation_dates + # Figure out creation time and modification time for description + task_ctime = get_creation_date(tid)[1] + + task_path = generate_task_path(tid) + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path) + if task_ctime == 0: + # ctime not available + task_ctime = mtime + stats_creation_dates.append(task_ctime) + + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path) + + cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime)) + sdate = time.strftime("%Y-%m-%d", time.localtime(mtime)) + if not stats_overtime.has_key(cdate): + # initialize date dict + stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) + stats_overtime[cdate]['Creation'] += 1 + if not stats_overtime.has_key(sdate): + # initialize date dict + stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) + stats_overtime[sdate][task_status] += 1 +#:end + + stats_total = len(tasks) + else: + # compute total number of tasks in group + path = generate_group_path(group) + + tasks = [] + for i in os.listdir(path): + if group == 'root': + tasks.append(i[ORDER_ID_LENGTH:]) + else: + # the group task is not counted in the statistics + if not baseconvert(0) in i[:ORDER_ID_LENGTH]: + tasks.append(i[ORDER_ID_LENGTH:]) + + stats_total = len(tasks) + + # compute number of tasks in each state, groups and links, recursively + for tid in tasks: + task_status = get_status(tid) + stats[task_status] += 1 + if is_this_task_a_group(tid): + stats[' Group'] += 1 + group_statistics(tid) + if is_linked(tid): + stats[' Linked'] += 1 + +#:stats_creation_dates + # Figure out creation time and modification time for description + task_ctime = get_creation_date(tid)[1] + + task_path = generate_task_path(tid) + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path) + if task_ctime == 0: + # ctime not available + task_ctime = mtime + stats_creation_dates.append(task_ctime) + + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path) + + cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime)) + sdate = time.strftime("%Y-%m-%d", time.localtime(mtime)) + if not stats_overtime.has_key(cdate): + # initialize date dict + stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) + stats_overtime[cdate]['Creation'] += 1 + if not stats_overtime.has_key(sdate): + # initialize date dict + stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) + stats_overtime[sdate][task_status] += 1 + + if not stats_total: + r.append('0 task in statistics.') + return r + + # csv format, not used for now + #col_names = ['Tasks'] + sorted(stats.keys()) + #col_names = [i strip for i in col_names] + #print ','.join(col_names) + #csv = '%d'%len(tasks) + #for k in sorted(stats.keys()) + # csv += ',%d'%stats[k] + #r append csv + #r append '' + + r.append('Number of items: %d\n'%stats_total) + for k in sorted(stats.keys()): + r.append('%s - %d'%(k, stats[k])) + r.append('') + + # speed + start_time = sorted(stats_creation_dates)[0] + now = time.time() + done_and_inactive = stats[TASK_STATUS[TASK_STATUS_DONE]] + stats[TASK_STATUS[TASK_STATUS_INACTIVE]] + if not done_and_inactive: + r.append('Nothing is done or inactive') + else: + # subtract tasks in state void, because tasks in void state are informative or groups + number_of_tasks = stats_total - stats[TASK_STATUS[TASK_STATUS_VOID]] + remaining_tasks = number_of_tasks - done_and_inactive + remaining_time = (now - start_time) / float(done_and_inactive) * remaining_tasks + r.append('Number of tasks (excluding voids): %d'%number_of_tasks) + r.append('Remaining tasks: %d'%remaining_tasks) + r.append('Remaining days: %.3f'%(remaining_time/86400)) + r.append('Start date: %s'%time.strftime("%Y-%m-%d", time.localtime(start_time))) + r.append("Today's date: %s"%time.strftime("%Y-%m-%d", time.localtime(now))) + r.append('Finish date: %s'%time.strftime("%Y-%m-%d", time.localtime(now + remaining_time))) + + # create csv stats from stats_overtime + f = open('%s/stats.csv'%data_location,'w') + f.write('date,%s\n'%','.join([i.strip() for i in STATS_OVERTIME_KEYS])) + + for d in sorted(stats_overtime.keys()): + f.write('%s,%s\n'%(d, ','.join([str(stats_overtime[d][i]) for i in STATS_OVERTIME_KEYS]))) + f.close() + + # create creation, done and inactive stats + f = open('%s/stats_creation_done_inactive.csv'%data_location,'w') + f.write('date,Creation,Done/Inactive\n') + + for d in sorted(stats_overtime.keys()): + f.write('%s,%d,%d\n'%(d, stats_overtime[d]['Creation'], stats_overtime[d][TASK_STATUS[TASK_STATUS_DONE]] + stats_overtime[d][TASK_STATUS[TASK_STATUS_INACTIVE]])) + f.close() + + r.append('') + return r + +# test core functions +#def test +# # test +# list_group('root') +# +# t1 = add_task('root', 'task1.txt') +# add_task('root', 'task2.txt') +# g3 = add_task('root', 'task3.txt') +# +# create_group(g3) +# t4 = add_task(g3, 'task4.txt') +# +# list_group('root') +# list_group(g3) +# +# #display_task(t1) +# #display_task(t4) +# +# # Delete task in a group of 2 tasks +# print '# Delete task in a group of 2 tasks' +# delete_task(t4) +# +# list_group('root') +# +# create_group(g3) +# t4 = add_task(g3, 'task4.txt') +# +# list_group('root') +# +# # Delete group first task +# print '# Delete group first task' +# delete_task(g3) +# +# list_group('root') +# +# # Delete group task of a group with more than 2 tasks +# print '# Delete group task of a group with more than 2 tasks' +# delete_task(t4) +# +# g3 = add_task('root', 'task3.txt') +# create_group(g3) +# t4 = add_task(g3, 'task4.txt') +# t2 = add_task(g3, 'task2.txt') +# +# list_group('root') +# list_group(g3) +# new_group_id = delete_task(g3) +# print 'New group id %s'%new_group_id +# +# list_group('root') +# list_group(new_group_id) +# +# # Delete group +# print '# Delete group' +# delete_group(new_group_id) +# +# list_group('root') +# +# print baseconvert(100) +# print baseconvert_to_dec(baseconvert(100)) +# print +# +# #edit_task(t1) +# +# # Change order +# print '# Change order' +# g3 = add_task('root', 'task3.txt') +# change_task_order('root',1,0) +# change_task_order('root',0,2) +# +# list_group('root') +# +# # Change status +# print '# Change status' +# create_group(g3) +# t4 = add_task(g3, 'task4.txt') +# t2 = add_task(g3, 'task2.txt') +# +# list_group('root') +# +# set_status(t1,TASK_STATUS_DONE) +# set_status(t4,TASK_STATUS_DONE) +# +# list_group('root') +# list_group(g3) +# +# # Reset status +# +# reset_group_status(g3) +# +# list_group('root') +# list_group(g3) +# +# reset_group_status('root') +# +# list_group('root') +# list_group(g3) +# +# # Create a group in group +# print '# Create a group in group' +# create_group(t4) +# t2 = add_task(t4,'task2.txt') +# +# list_group('root') +# list_group(g3) +# list_group(t4) +# +# # Delete task in a group of 2 tasks not in root +# print '# Delete task in a group of 2 tasks not in root' +# delete_task(t2) +# +# list_group('root') +# list_group(g3) +# +# # Delete group with groups in it +# print '# Delete group with groups in it' +# create_group(t4) +# t2 = add_task(t4,'task2.txt') +# +# list_group('root') +# list_group(g3) +# list_group(t4) +# +# delete_group(g3) +# +# list_group('root') +# +# # Search string +# print '# Search string' +# +# search_string('AND') +# +# # List tree +# print '# List tree' +# g3 = add_task('root', 'task3.txt') +# create_group(g3) +# t4 = add_task(g3, 'task4.txt') +# t2 = add_task(g3, 'task2.txt') +# create_group(t4) +# t2 = add_task(t4,'task2.txt') +# +# list_tree('root') +# +# # Show group for a task +# print '# Show group for a task' +# +# print 'Show group for %s - %s' %(t2,get_task_title(t2)) +# show_group_for_task(t2) +# print 'Show group for %s - %s' %(t4,get_task_title(t4)) +# show_group_for_task(t4) +# print 'Show group for %s - %s' %(t1,get_task_title(t1)) +# show_group_for_task(t1) + + # create task + #create_task(t4) + #list_group(t4) + +## start - always called at startup. +# @ingroup EDI_CORE +# @param[in] interface selects current user interface. cli loads the configuration from user home, web loads the configuration located in edi_web folder. +# creates default .easydoneit.ini<br> +# creates database folders<br> +# loads .easydoneit.ini +def start(interface='cli'): + global user_interface + user_interface = interface + + if interface=='web': + # Do not try to access ~/.easydoneit.ini + inipath = '.easydoneit.ini' + else: + inipath = '~/.easydoneit.ini' + # create ~/.easydoneit.ini if it doesnt exist, (not in web interface mode) + if not os.path.isfile(os.path.expanduser('~/.easydoneit.ini')): + f = open(os.path.expanduser('~/.easydoneit.ini'),'w') + f.write('[data]\n') + f.write('location=~/easydoneit_data\n') + f.write('1=~/easydoneit_data\n') + f.write('\n') + f.write('[locations]\n') + f.write('selected=1\n') + f.write('default_add_in=1\n') + f.write('\n') + f.write('[filters]\n') + # enable all status filters + fi = zip(TASK_STATUS,status_filters) + for name,nfilter in fi: + # strip to remove spaces in status strings + f.write("%s=%s\n"%(name.strip(),nfilter)) + f.write('\n') + f.write('[colors]\n') + co = zip(TASK_STATUS,status_fgColors) + for name,color in co: + f.write('%s_fgColor=%d,%d,%d,%d\n' % (name.strip(),color[0],color[1],color[2],color[3])) + co = zip(TASK_STATUS,status_bgColors) + for name,color in co: + f.write('%s_bgColor=%d,%d,%d,%d\n' % (name.strip(),color[0],color[1],color[2],color[3])) + + f.write('\n[settings]\n') + f.write('editor=vi\n') + + f.close() + # default permissions 600 rw for user no access for group and world + os.chmod(os.path.expanduser('~/.easydoneit.ini'),stat.S_IRUSR|stat.S_IWUSR) + + # load config from inipath + config = ConfigParser.ConfigParser() + config.readfp(open(os.path.expanduser(inipath))) + + # convert ~ to home path + global data_location + data_location = os.path.expanduser(config.get('data','location')) + + # load available databases + global databases + data_section = config.items('data') + # remove location which is a path to a database + DATABASE_NAME = 0 + DATABASE_PATH = 1 + # clear databases for reentrant testing + databases = [] + for d in data_section: + if d[DATABASE_NAME] != 'location': + databases.append(d) + + # load add_top_or_bottom + if 'add_top_or_bottom' in dict(config.items('locations')).keys(): + global add_top_or_bottom + add_top_or_bottom = config.get('locations','add_top_or_bottom') + + # load selected database paths + global selected + global default_add_in + global selected_path + selected = config.get('locations','selected').split(',') + default_add_in = config.get('locations','default_add_in') + for d in selected: + selected_path.append(os.path.expanduser(config.get('data',d))) + + # load autolink groups + if 'autolink' in dict(config.items('locations')).keys(): + global autolink + autolink_cfg = config.get('locations','autolink') + autolink = [ autolink_cfg[i:i+ID_LENGTH] for i in range(0,len(autolink_cfg), ID_LENGTH+1)] + + # load list groups + if 'list' in dict(config.items('locations')).keys(): + global list_of_groups + list_cfg = config.get('locations','list') + list_of_groups = [ list_cfg[i:i+ID_LENGTH] for i in range(0,len(list_cfg), ID_LENGTH+1)] + + # load status filters and status colors + config_filter_names = dict(config.items('filters')).keys() + config_color_names = dict(config.items('colors')).keys() + for n,nfilter in enumerate(TASK_STATUS): + # strip to remove spaces in status strings + # check that task_status filter is in config file, to avoid problems between config file versions + if nfilter.strip().lower() in config_filter_names: + status_filters[n] = config.get('filters',nfilter.strip()) + if '%s_fgcolor'%nfilter.strip().lower() in config_color_names: + status_fgColors[n] = tuple([int(i) for i in config.get('colors','%s_fgcolor'%nfilter.strip()).split(',')]) + if '%s_bgcolor'%nfilter.strip().lower() in config_color_names: + status_bgColors[n] = tuple([int(i) for i in config.get('colors','%s_bgcolor'%nfilter.strip()).split(',')]) + + # Set text editor + global editor + editor = config.get('settings','editor') + + # set user name and email + global user + if 'username' in dict(config.items('settings')).keys(): + user = config.get('settings','username') + else: + user = getpass.getuser() + if 'useremail' in dict(config.items('settings')).keys(): + global email + email = config.get('settings','useremail') + + # init + init() + diff --git a/root.txt b/root.txt @@ -0,0 +1 @@ +First Level