Check-in [d74ecb0949]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Generalized Morg so it's no longer task-specific. Rather, it aspires to be a general hierarchical list manager and, by default, its predefined properties set it up to manage shopping lists and TODO lists.
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1:d74ecb0949634747181a09e0e3ebfc9e95c58ebe
User & Date: mvnathan 2014-09-19 07:28:35
Context
2014-09-19
08:11
Added week, status, and priority to list of predefined properties. check-in: 08ee0f3c5e user: mvnathan tags: dev
07:28
Generalized Morg so it's no longer task-specific. Rather, it aspires to be a general hierarchical list manager and, by default, its predefined properties set it up to manage shopping lists and TODO lists. check-in: d74ecb0949 user: mvnathan tags: dev
04:57
Pass database object to all the commands. check-in: 1a510a86e6 user: mvnathan tags: dev
Changes

Changes to py/morg.py.

1
2
3
4
5
6
7
8
9
10
11
..
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/env python
#
# @file  morg.py
# @brief Morg TODO list manager's main function and related code.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
def main():
    try:
        args = morglib.args.parse(sys.argv[1:], str(version()))
        morglib.log.init(args.debug_level, args.log_file)
        logger.info('starting Morg')
        morglib.args.dump(args)

        logger.info('opening database {}'.format(args.tasks_file))
        db = morglib.db(args.tasks_file, morglib.properties.defaults)

        if (args.command):
            morglib.interpreter.execute(args.command[0], args.command, db)
        else:
            morglib.command.repl(morglib.interpreter, db)

    except morglib.args.args_error, e:



|







 







|
|







1
2
3
4
5
6
7
8
9
10
11
..
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/env python
#
# @file  morg.py
# @brief Morg list manager's main function and related code.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
def main():
    try:
        args = morglib.args.parse(sys.argv[1:], str(version()))
        morglib.log.init(args.debug_level, args.log_file)
        logger.info('starting Morg')
        morglib.args.dump(args)

        logger.info('opening database {}'.format(args.database))
        db = morglib.db(args.database, morglib.properties.defaults)

        if (args.command):
            morglib.interpreter.execute(args.command[0], args.command, db)
        else:
            morglib.command.repl(morglib.interpreter, db)

    except morglib.args.args_error, e:

Changes to py/morglib/__init__.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#
# @file  __init__.py
# @brief Initialization for morglib module.
#
# Much of Morg's functionality is encapsulated within the morglib
# package. Thus, clients can use Morg as a standalone application or
# build a custom task management application using its library.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
import pkgutil
import inspect
import importlib
import os
import sys

# Pull in various morglib modules so clients don't have to import them
# explicitly and to facilitate automatic initialization of Morg
# interpreter's dispatch table.
for _, modname, __ in pkgutil.iter_modules([os.path.dirname(__file__)]):
    full_name = '{}.{}'.format(__name__, modname)
    if (full_name not in sys.modules):
        importlib.import_module(full_name)

# Pull in exception base class and alias it so clients can use it simply






|







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#
# @file  __init__.py
# @brief Initialization for morglib module.
#
# Much of Morg's functionality is encapsulated within the morglib
# package. Thus, clients can use Morg as a standalone application or
# build a custom list management application using its library.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
import pkgutil
import inspect
import importlib
import os
import sys

# Pull in various morglib modules so clients don't have to import them
# explicitly and also to facilitate automatic initialization of the Morg
# interpreter's dispatch table.
for _, modname, __ in pkgutil.iter_modules([os.path.dirname(__file__)]):
    full_name = '{}.{}'.format(__name__, modname)
    if (full_name not in sys.modules):
        importlib.import_module(full_name)

# Pull in exception base class and alias it so clients can use it simply

Changes to py/morglib/args.py.

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
..
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
...
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
...
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    @param  ver (string) Version string for -v option.
    @return argparse.Namespace object containing parsed options.

    This function parses the command-line arguments and returns an
    argparse.Namespace object with the following keys:

        @li <tt> config_dir  </tt>
        @li <tt> tasks_file  </tt>
        @li <tt> debug_log   </tt>
        @li <tt> debug_level </tt>

    The value of the <tt>config_dir</tt> key is a string specifying the
    directory where the user's Morg configs are stored.

    The <tt>tasks_file</tt> key is a string specifying the SQLite file
    that Morg should use as its tasks database.

    The <tt>debug_log</tt> key is a string pointing to the log file into
    which Morg should output debug messages.

    The <tt>debug_level</tt> key is an integer from the standard Python
    logging module that specifies how much debug info is desired.

................................................................................

    defaults = _init_defaults()
    parser.add_argument('-c', '--config-dir',
                        default = defaults['config_dir'],
                        help = ('directory containing Morg configs ' +
                                '(default: %(default)s)'),
                        metavar = 'DIR')
    parser.add_argument('-f', '--tasks-file',
                        default = defaults['tasks_file'],
                        help = 'tasks database (default: %(default)s)',
                        metavar = 'FILE')
    parser.add_argument('-l', '--log-file',
                        default = defaults['debug_log'],
                        help = 'debug log (default: %(default)s)',
                        metavar = 'LOG')
    parser.add_argument('-d', '--debug-level',
                        type = _validate_debug_level,
................................................................................

    @return Dictionary containing default values.

    This function returns a dictionary of strings to strings with the
    following keys:

        @li <tt> config_dir  </tt>
        @li <tt> tasks_file  </tt>
        @li <tt> debug_log   </tt>
        @li <tt> debug_level </tt>

    First, it initializes the values corresponding to the above keys with
    hard-coded strings. Then, it examines the environment to look for
    definitions of Morg's supported environment variables and replaces
    the hard-coded values with the values of the environment variables.
................................................................................

    '''
    home = os.environ['HOME'] if ('HOME' in os.environ) else '.'
    dot_morg = os.path.join(home, '.morg')

    # Hard-coded defaults
    defaults = {'config_dir' : dot_morg,
                'tasks_file' : os.path.join(dot_morg, 'tasks.sqlite'),
                'debug_log'  : os.path.join(dot_morg, 'debug.log'),
                'debug_level': '2'}

    # Environment variables override hard-coded defaults
    for k in defaults:
        v = 'MORG_{}'.format(k.upper())
        if (v in os.environ):
            defaults[k] = os.environ[v]
    return  defaults


def _help_before_options():
    help_text = '''
Morg is a TODO list manager that helps you keep track of all the things
you need to get done. Morg does not adhere to any particular time
management methodology. Instead, it focuses on implementing a flexible
data model and providing functions to manipulate that model so that you
can adapt it to your particular needs and style.
'''
    return help_text

................................................................................

Some of the above options can also be specified via environment
variables:

        Option        Environment Variable
        ------        --------------------
        -c            MORG_CONFIG_DIR
        -f            MORG_TASKS_FILE
        -l            MORG_DEBUG_LOG
        -d            MORG_DEBUG_LEVEL

Command-line arguments override environment variables, which, in turn,
override the hard-coded defaults.

Usually, after the command-line options, you will provide arguments that
Morg interprets as a command to execute. These would be commands to, for
example, create a new task, search your tasks list, edit a task, and so
on.

Without a command, Morg will drop you into interactive mode, which will
issue a prompt and wait for you to type in commands until you exit.
'''
    return help_text








|






|
|







 







|
|
|







 







|







 







|













|







 







|








|







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
..
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
...
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
...
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    @param  ver (string) Version string for -v option.
    @return argparse.Namespace object containing parsed options.

    This function parses the command-line arguments and returns an
    argparse.Namespace object with the following keys:

        @li <tt> config_dir  </tt>
        @li <tt> database    </tt>
        @li <tt> debug_log   </tt>
        @li <tt> debug_level </tt>

    The value of the <tt>config_dir</tt> key is a string specifying the
    directory where the user's Morg configs are stored.

    The <tt>database</tt> key is a string specifying the SQLite file that
    Morg should use as its items database.

    The <tt>debug_log</tt> key is a string pointing to the log file into
    which Morg should output debug messages.

    The <tt>debug_level</tt> key is an integer from the standard Python
    logging module that specifies how much debug info is desired.

................................................................................

    defaults = _init_defaults()
    parser.add_argument('-c', '--config-dir',
                        default = defaults['config_dir'],
                        help = ('directory containing Morg configs ' +
                                '(default: %(default)s)'),
                        metavar = 'DIR')
    parser.add_argument('-f', '--database',
                        default = defaults['database'],
                        help = 'items database (default: %(default)s)',
                        metavar = 'FILE')
    parser.add_argument('-l', '--log-file',
                        default = defaults['debug_log'],
                        help = 'debug log (default: %(default)s)',
                        metavar = 'LOG')
    parser.add_argument('-d', '--debug-level',
                        type = _validate_debug_level,
................................................................................

    @return Dictionary containing default values.

    This function returns a dictionary of strings to strings with the
    following keys:

        @li <tt> config_dir  </tt>
        @li <tt> database    </tt>
        @li <tt> debug_log   </tt>
        @li <tt> debug_level </tt>

    First, it initializes the values corresponding to the above keys with
    hard-coded strings. Then, it examines the environment to look for
    definitions of Morg's supported environment variables and replaces
    the hard-coded values with the values of the environment variables.
................................................................................

    '''
    home = os.environ['HOME'] if ('HOME' in os.environ) else '.'
    dot_morg = os.path.join(home, '.morg')

    # Hard-coded defaults
    defaults = {'config_dir' : dot_morg,
                'database'   : os.path.join(dot_morg, 'items.sqlite'),
                'debug_log'  : os.path.join(dot_morg, 'debug.log'),
                'debug_level': '2'}

    # Environment variables override hard-coded defaults
    for k in defaults:
        v = 'MORG_{}'.format(k.upper())
        if (v in os.environ):
            defaults[k] = os.environ[v]
    return  defaults


def _help_before_options():
    help_text = '''
Morg is a list manager that helps you keep track of all the things
you need to get done. Morg does not adhere to any particular time
management methodology. Instead, it focuses on implementing a flexible
data model and providing functions to manipulate that model so that you
can adapt it to your particular needs and style.
'''
    return help_text

................................................................................

Some of the above options can also be specified via environment
variables:

        Option        Environment Variable
        ------        --------------------
        -c            MORG_CONFIG_DIR
        -f            MORG_DATABASE
        -l            MORG_DEBUG_LOG
        -d            MORG_DEBUG_LEVEL

Command-line arguments override environment variables, which, in turn,
override the hard-coded defaults.

Usually, after the command-line options, you will provide arguments that
Morg interprets as a command to execute. These would be commands to, for
example, create a new item, search your items list, edit an item, and so
on.

Without a command, Morg will drop you into interactive mode, which will
issue a prompt and wait for you to type in commands until you exit.
'''
    return help_text

Changes to py/morglib/command.py.

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
...
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    <tt>__call__</tt> method, which will be passed a list of strings
    containing the command-line used to invoke that command. The first
    member of this list will be the command name itself; the remaining
    elements are the command-line arguments.

    In addition to the command-line arguments, each command's
    <tt>__call__</tt> method will receive a database object, which it can
    use to search and/or manipulate the tasks database as required.

    To automate help processing, each command should be able to handle a
    <tt>--help</tt> option.

    '''
    def __call__(self, argv, db):
        '''Execute command.

        @param argv (string list) Arguments provided via command-line.
        @param db (morglib.database) Interface object for tasks database.

        The call method acts as the means to execute commands. In this
        base class, we simply raise an exception to indicate that a
        subclass has failed to implement the desired functionality.

        '''
        raise unimplemented_cmd(argv[0])
................................................................................
        return self._dispatch_table.iteritems()

    def execute(self, key, args, db):
        '''Execute specified command.

        @param key (string) Command name.
        @param args (string list) Command-line arguments.
        @param db (morglib.database) Tasks database interface object.

        '''
        try:
            logger.debug('executing command: {}'.format(args))
            f = self[key]
            f(args, db)
        except KeyError:
................................................................................

        When instantiating the <tt>repl</tt> class, you should supply it
        with the <tt>morglib.interpreter</tt> so that this object knows
        how to interpret and execute the commands it receives
        interactively.

        Additionally, all Morg command objects have to be passed the
        tasks database so they can operate on it as required. Thus, you
        should also pass the database's interface object to this
        constructor.

        '''
        cmd.Cmd.__init__(self)
        self.prompt = 'morg> '
        self._interpreter = interpreter
        self._db = db







|









|







 







|







 







|
|







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
...
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    <tt>__call__</tt> method, which will be passed a list of strings
    containing the command-line used to invoke that command. The first
    member of this list will be the command name itself; the remaining
    elements are the command-line arguments.

    In addition to the command-line arguments, each command's
    <tt>__call__</tt> method will receive a database object, which it can
    use to search and/or manipulate the Morg database as required.

    To automate help processing, each command should be able to handle a
    <tt>--help</tt> option.

    '''
    def __call__(self, argv, db):
        '''Execute command.

        @param argv (string list) Arguments provided via command-line.
        @param db (morglib.database) Interface object for Morg's database.

        The call method acts as the means to execute commands. In this
        base class, we simply raise an exception to indicate that a
        subclass has failed to implement the desired functionality.

        '''
        raise unimplemented_cmd(argv[0])
................................................................................
        return self._dispatch_table.iteritems()

    def execute(self, key, args, db):
        '''Execute specified command.

        @param key (string) Command name.
        @param args (string list) Command-line arguments.
        @param db (morglib.database) Morg's database interface object.

        '''
        try:
            logger.debug('executing command: {}'.format(args))
            f = self[key]
            f(args, db)
        except KeyError:
................................................................................

        When instantiating the <tt>repl</tt> class, you should supply it
        with the <tt>morglib.interpreter</tt> so that this object knows
        how to interpret and execute the commands it receives
        interactively.

        Additionally, all Morg command objects have to be passed the
        database object so they can operate on it as required. Thus, you
        should also pass Morg's database interface object to this
        constructor.

        '''
        cmd.Cmd.__init__(self)
        self.prompt = 'morg> '
        self._interpreter = interpreter
        self._db = db

Changes to py/morglib/database.py.

1
2
3
4
5
6
7
8
9
10
..
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
...
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
...
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
...
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
...
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
...
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
...
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
...
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
...
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
...
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
#
# @file  database.py
# @brief Encapsulation of Morg's tasks database.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
        @param msg (string) Message describing the error.
        @param reason (any) Additional information about the error.

        Typically, reason will be an APSW exception object indicating
        exactly what went wrong. However, this parameter can be any
        object; this method will convert it to a string. Additionally,
        reason is optional (defaults to None); if not supplied, the
        database_error will not contain any further details about the
        problem.

        '''
        if (reason):
            msg = '{} (reason: {})'.format(msg, str(reason))
        morg_error.error_base.__init__(self, msg)

class connection_error(database_error):
    '''For reporting inability to open Morg's SQLite database.'''

    def __init__(self, tasks_file, reason = None):
        '''Construct connection error.

        @param tasks_file (string) Name of Morg's SQLite database file.
        @param reason (any) Additional information about connection failure.

        Typically, reason will be an exception object indicating why we
        were unable to connect to the tasks_file SQLite database.
        However, this parameter can be any object; this method will
        convert it to a string. Additionally, reason is optional
        (defaults to None); if not supplied, the connection_error will
        not contain any further details about why Morg was unable to open
        the tasks file.

        '''
        msg = 'unable to open {}'.format(tasks_file)
        database_error.__init__(self, msg, reason)

class sanity_check_error(database_error):
    '''For reporting databases that Morg cannot use.'''

    def __init__(self, tasks_file, reason = None):
        '''Construct sanity check error.

        @param tasks_file (string) Name of Morg's SQLite database file.
        @param reason (any) Additional information about connection failure.

        Typically, reason will be a string explaining the problem with
        the tasks file. However, this parameter can be any object and
        will, via the base class constructor, be converted to a string.

        '''
        msg = 'cannot use database {}'.format(tasks_file)
        database_error.__init__(self, msg, reason)

class sql_error(database_error):
    '''For reporting inability to execute SQL queries.'''

    def __init__(self, query, reason = None):
        '''Construct SQL error.
................................................................................
        @param query (string) The SQL query that could not be executed.
        @param reason (any) Additional information about SQL failure.

        Typically, reason will be an exception object indicating why we
        were unable to execute the SQL query. However, this parameter can
        be any object (it will be converted to a string). Additionally,
        reason is optional (defaults to None); if not supplied, the
        sql_error will not contain any further details about why Morg was
        unable to execute the query.

        '''
        msg = 'unable to execute SQL query "{}"'.format(query)
        database_error.__init__(self, msg, reason)

class property_error(database_error):
    '''For reporting errors related to task properties.'''

    def __init__(self, n, t, r = None, e = None):
        '''Construct a property error.

        @param n (string) Property name.
        @param t (string) Property type.
        @param r (string) Client-supplied range specification (if any).
................................................................................
        describing the problem.

        '''
        msg = ('property error: name = "{}", type = {}, range = {}'.
               format(n, t, r))
        database_error.__init__(self, msg, e)

#--------------------------- TASKS DATABASE -----------------------------

class database:
    '''Encapsulation of interface to Morg tasks database.

    This class provides clients a high-level API for dealing with the
    Morg database. With it, you can add and remove tasks and properties
    and even execute arbitrary SQL queries.

    The Morg application instantiates this class early on and then passes
    that object to all <tt>morglib.command</tt> instances to allow them
    to easily implement their respective functionalities by manipulating
    the Morg database as required.

................................................................................
    SCHEMA_VERSION = 1

    # Hard-coded string to help identify whether an SQLite file is a Morg
    # database or not.
    MAGIC_PHRASE = ('This is a Morg database.\n' +
                    'Alter it manually at your own peril.')

    def __init__(self, tasks_file, properties):
        '''Connect to Morg database.

        @param tasks_file (string) Name of SQLite file containing tasks.
        @param properties (dict) Initial set of task properties.

        This method opens the SQLite file passed to it. If the file
        doesn't already exist, it will be created (which could involve
        creating the entire directory hierarchy specified in the
        tasks_file parameter).

        Failure to open the SQLite file will result in a
        <tt>connection_error</tt> exception.

        Once the database has been successfully opened, the constructor
        will proceed to check if the database is empty or not. In an
        empty database, Morg will create a watermark table so that it can
................................................................................
        <tt>sanity_check_error</tt>.

        Refer to the documentation of the <tt>watermark</tt> class for
        further information about Morg's simple watermarking system.

        After the watermark creation or verification phase, the
        constructor proceeds to creating the basic database structure
        required by Morg. At the very least, Morg needs a <tt>task</tt>
        table to record all your tasks as well as several property tables
        that define and record the various properties associated with
        tasks. Preexisting task and property tables will be left
        untouched.

        However, the database constructor allows you to define new
        properties during initialization. These initial properties are
        specified via the properties parameter, which is a dict that maps
        strings to 2-tuples. The dict keys are the names of properties.
        The 2-tuple corresponding to each key, i.e., property name,
        should contain the property type and its range.

        The property type can be one of the following (case-insensitive)
        strings:

        @li <tt>integer</tt>
        @li <tt>real</tt>
        @li <tt>text</tt>

        The property range may be either <tt>None</tt> or a string
        describing the range of values that particular property may take
        on. Commas can be used to separate items in a list of discrete
        values. Double dots can be used for numeric ranges. Note that
        even if a particular property does not have any restrictions on
        its range, its 2-tuple in the properties dict cannot omit the
        <tt>None</tt> for the second element; i.e., each key's value
        <b>must</b> be a 2-tuple.

        Here are a few illustrative examples:
................................................................................
        @verbatim
            props = {'role'       : ('text',    'President,Glutton,Buffoon'),
                     'priority'   : ('integer', '1,2,3,5,7'),
                     'importance' : ('real',    '-1..+1'),
                     'description': ('text',    None)}
        @endverbatim

        In the above, a task may be associated with one of three roles,
        viz., President, Glutton, or Buffoon. Its priority may be one of
        the prime numbers below ten. The task's importance may lie in the
        range [-1, +1]. And, finally, it may sport a textual description
        that can be any string.

        '''
        try:
            logger.info('connecting to database {}'.format(tasks_file))
            self._db = apsw.Connection(tasks_file)

        except apsw.Error, e:
            logger.error('first attempt to open {} failed ({})'.
                         format(tasks_file, e))

            dirs = os.path.dirname(tasks_file)
            logger.debug('creating directory hierarchy {}'.format(dirs))
            try:
                os.makedirs(dirs)
            except os .error, e:
                logger.error('unable to create directory hierarchy {} ({})'.
                             format(dirs, e))
                raise connection_error(tasks_file, e)

            try:
                logger.info('second attempt to open {}'.format(tasks_file))
                self._db = apsw.Connection(tasks_file)
            except apsw.Error, e:
                logger .error('second attempt to open {} also failed ({})'.
                              format  (tasks_file, e))
                raise connection_error(tasks_file, e)

        if (self.is_empty()):
            logger.info('{} is empty'.format(tasks_file))
            self._init_morg_table()
        else:
            logger.info('{} has data'.format(tasks_file))
            self._sanity_check(tasks_file)

        self._init_task_table()
        self._init_property_tables(properties)

    # Create and populate watermark table
    def _init_morg_table(self):
        with self._db:
            logger.info('creating morg table')
            wm = watermark()
................................................................................
            columns = wm.columns()
            insert  = ('insert into morg ({}) values ({})'.
                       format(','.join(columns),
                              ','.join('?' * len(columns))))
            self.execute(insert, tuple(wm))

    # Try and confirm we're dealing with a Morg database
    def _sanity_check(self, tasks_file):
        logger.info('checking morg table')
        try:
            morg = self.execute('select * from morg')
        except sql_error, e:
            raise sanity_check_error(tasks_file,
                                     'unable to get data from morg table')

        n = len(morg)
        logger.debug('morg table has {} rows'.format(n))
        if (n < 1):
            raise sanity_check_error(tasks_file, 'morg table has no data')
        if (n > 1):
            raise sanity_check_error(tasks_file,
                                     'morg table has too much data')

        wm = watermark(morg[0])
        wm.dump()
        if (not wm):
            raise sanity_check_error(tasks_file, 'magic phrase mismatch')
        logger.info('looks like this is a morg database')

    # Create task table if necessary
    def _init_task_table(self):
        logger.info('creating task table')
        create =  '''create table if not exists task(
                         id integer primary key autoincrement)'''
        self.execute(create)

    # Create and/or update the property tables
    def _init_property_tables(self, properties):
        logger.info('creating property table')
        create =  '''create table if not exists property(
................................................................................
        column constraint and result in a <tt>sql_error</tt>. Trying to
        use a zero-length string will result in a
        <tt>property_error</tt>.

        The property type can be one of the following (case-insensitive)
        strings:

        @li <tt>integer</tt>
        @li <tt>real</tt>
        @li <tt>text</tt>

        If applicable, you may specify the range of values a property may
        assume. For numeric properties, you may use the syntax
        <tt>min..max</tt> to specify the inclusive range
        <tt>[min, max]</tt>.

        All three property types may also take on discrete values. You
................................................................................
            select  = '''select id from property where(
                             name = (?) and type = (?))'''
            i, = self.execute(select, (n, t))[0]
            logger.debug('creating table property_{} for property "{}"'.
                         format(i, n))
            create  = ('''create table if not exists property_{}(
                              id integer primary key autoincrement,
                              task_id integer not null,
                              value {} not null {})'''.
                       format(i, t, range_constraint('value', t, r)))
            self.execute(create)

    def is_empty(self):
        '''Check if database has anything in it.

................................................................................
    Please note that the watermark system Morg uses is not foolproof. You
    can easily enough insert and populate such a table in an arbitrary
    SQLite file and "trick" Morg into using that file as its database. As
    a matter of fact, this class is designed specifically to facilitate
    such "Morgification" without requiring you to jump through
    unnecessary hoops.

    For example, assuming the <tt>morglib</tt> module is in your Python
    path, here is a quick Python program to generate a SQL script that
    you can then use to "Morgify" an SQLite file:

    @verbatim
        import morglib
        f = open('morgify.sql', 'w')
        f.write(morglib.database.watermark().sql())
................................................................................
            wm  = morglib.database.watermark()
            cur = db.cursor()
            cur.execute(wm.create())
            cur.execute('insert into morg values (?,?,?,?,?)', tuple(wm))
    @endverbatim

    After you run the above program, <tt>foo.sqlite</tt> will have the
    necessary magic to allow Morg to use it as a tasks database.

    To prevent Morg from using an SQLite file, all you have to do is
    delete the <tt>morg</tt> table from it (or just mangle its contents
    so that the sanity check fails). <tt>morglib</tt>, however, does not
    provide a straightforward means of demorgification of a database. For
    that, you'll have to resort to the SQLite command-line.

................................................................................
        '''Construct a watermark object.

        @param metadata (tuple or dict) Watermark table's data.

        The <tt>morg</tt> table contains exactly one row, which has the
        following columns:

        @li <tt>schema_version</tt>
        @li <tt>timestamp</tt>
        @li <tt>hostname</tt>
        @li <tt>username</tt>
        @li <tt>magic_phrase</tt>

        The schema version is an integer that helps track the structure
        of the database. It can be used, for example, to upgrade older
        data models to more current ones.

        The time stamp records the creation time (GMT) of the database in
        the format <tt>yyyy-mm-dd HH:MM:SS</tt>.
................................................................................
    '''Parse range specification and return column constraint clause.

    @param  column_name (string) Name of column that needs a constraint.
    @param  column_type (string) Type of column that needs a constraint.
    @param  range_spec  (string or None) Range spec to be translated to SQL.
    @return String containing SQL constraint clause for column.

    Each task property has a name, a type, and, optionally, a range. Morg
    allows users to specify ranges using double dots to indicate min and
    max values for a numeric property and commas to list discrete values
    for any supported type.

    This function converts the range specification strings described
    above into a valid SQL constraint clause for the named column.



|







 







|
|









|


|



|
|
|
|
|
|


|





|


|



|



|







 







|
|






|







 







|


|


|







 







|


|
|




|







 







|
|

|












|
|
|



|







 







|

|





|
|



|

|






|


|
|


|
|


|


|
|

|







 







|




|





|

|





|


|
|
|
|







 







|
|
|







 







|







 







|







 







|







 







|
|
|
|
|







 







|







1
2
3
4
5
6
7
8
9
10
..
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
...
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
...
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
...
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
...
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
...
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
...
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
...
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
...
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
...
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
#
# @file  database.py
# @brief Encapsulation of Morg's items database.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
        @param msg (string) Message describing the error.
        @param reason (any) Additional information about the error.

        Typically, reason will be an APSW exception object indicating
        exactly what went wrong. However, this parameter can be any
        object; this method will convert it to a string. Additionally,
        reason is optional (defaults to None); if not supplied, the
        <tt>database_error</tt> will not contain any further details
        about the problem.

        '''
        if (reason):
            msg = '{} (reason: {})'.format(msg, str(reason))
        morg_error.error_base.__init__(self, msg)

class connection_error(database_error):
    '''For reporting inability to open Morg's SQLite database.'''

    def __init__(self, sqlite_file, reason = None):
        '''Construct connection error.

        @param sqlite_file (string) Name of Morg's SQLite database file.
        @param reason (any) Additional information about connection failure.

        Typically, reason will be an exception object indicating why we
        were unable to connect to the SQLite database. However, this
        parameter can be any object; this method will convert it to a
        string. Additionally, reason is optional (defaults to None); if
        not supplied, the <tt>connection_error</tt> will not contain any
        further details about why Morg was unable to open the SQLite
        file.

        '''
        msg = 'unable to open {}'.format(sqlite_file)
        database_error.__init__(self, msg, reason)

class sanity_check_error(database_error):
    '''For reporting databases that Morg cannot use.'''

    def __init__(self, sqlite_file, reason = None):
        '''Construct sanity check error.

        @param sqlite_file (string) Name of Morg's SQLite database file.
        @param reason (any) Additional information about connection failure.

        Typically, reason will be a string explaining the problem with
        the SQLite file. However, this parameter can be any object and
        will, via the base class constructor, be converted to a string.

        '''
        msg = 'cannot use database {}'.format(sqlite_file)
        database_error.__init__(self, msg, reason)

class sql_error(database_error):
    '''For reporting inability to execute SQL queries.'''

    def __init__(self, query, reason = None):
        '''Construct SQL error.
................................................................................
        @param query (string) The SQL query that could not be executed.
        @param reason (any) Additional information about SQL failure.

        Typically, reason will be an exception object indicating why we
        were unable to execute the SQL query. However, this parameter can
        be any object (it will be converted to a string). Additionally,
        reason is optional (defaults to None); if not supplied, the
        <tt>sql_error</tt> will not contain any further details about why
        Morg was unable to execute the query.

        '''
        msg = 'unable to execute SQL query "{}"'.format(query)
        database_error.__init__(self, msg, reason)

class property_error(database_error):
    '''For reporting errors related to item properties.'''

    def __init__(self, n, t, r = None, e = None):
        '''Construct a property error.

        @param n (string) Property name.
        @param t (string) Property type.
        @param r (string) Client-supplied range specification (if any).
................................................................................
        describing the problem.

        '''
        msg = ('property error: name = "{}", type = {}, range = {}'.
               format(n, t, r))
        database_error.__init__(self, msg, e)

#--------------------------- ITEMS DATABASE -----------------------------

class database:
    '''Encapsulation of interface to Morg's database of items.

    This class provides clients a high-level API for dealing with the
    Morg database. With it, you can add and remove items and properties
    and even execute arbitrary SQL queries.

    The Morg application instantiates this class early on and then passes
    that object to all <tt>morglib.command</tt> instances to allow them
    to easily implement their respective functionalities by manipulating
    the Morg database as required.

................................................................................
    SCHEMA_VERSION = 1

    # Hard-coded string to help identify whether an SQLite file is a Morg
    # database or not.
    MAGIC_PHRASE = ('This is a Morg database.\n' +
                    'Alter it manually at your own peril.')

    def __init__(self, sqlite_file, properties):
        '''Connect to Morg database.

        @param sqlite_file (string) Name of SQLite file containing items.
        @param properties (dict) Initial set of item properties.

        This method opens the SQLite file passed to it. If the file
        doesn't already exist, it will be created (which could involve
        creating the entire directory hierarchy specified in the
        <tt>sqlite_file</tt> parameter).

        Failure to open the SQLite file will result in a
        <tt>connection_error</tt> exception.

        Once the database has been successfully opened, the constructor
        will proceed to check if the database is empty or not. In an
        empty database, Morg will create a watermark table so that it can
................................................................................
        <tt>sanity_check_error</tt>.

        Refer to the documentation of the <tt>watermark</tt> class for
        further information about Morg's simple watermarking system.

        After the watermark creation or verification phase, the
        constructor proceeds to creating the basic database structure
        required by Morg. At the very least, Morg needs an <tt>item</tt>
        table to record all your items as well as several property tables
        that define and record the various properties associated with
        items. Preexisting item and property tables will be left
        untouched.

        However, the database constructor allows you to define new
        properties during initialization. These initial properties are
        specified via the properties parameter, which is a dict that maps
        strings to 2-tuples. The dict keys are the names of properties.
        The 2-tuple corresponding to each key, i.e., property name,
        should contain the property type and its range.

        The property type can be one of the following (case-insensitive)
        strings:

        @li <tt> integer </tt>
        @li <tt> real    </tt>
        @li <tt> text    </tt>

        The property range may be either <tt>None</tt> or a string
        describing the range of values that particular property may take
        on. Commas can be used to separate elements in a list of discrete
        values. Double dots can be used for numeric ranges. Note that
        even if a particular property does not have any restrictions on
        its range, its 2-tuple in the properties dict cannot omit the
        <tt>None</tt> for the second element; i.e., each key's value
        <b>must</b> be a 2-tuple.

        Here are a few illustrative examples:
................................................................................
        @verbatim
            props = {'role'       : ('text',    'President,Glutton,Buffoon'),
                     'priority'   : ('integer', '1,2,3,5,7'),
                     'importance' : ('real',    '-1..+1'),
                     'description': ('text',    None)}
        @endverbatim

        In the above, an item may be associated with one of three roles,
        viz., President, Glutton, or Buffoon. Its priority may be one of
        the prime numbers below ten. The item's importance may lie in the
        range [-1, +1]. And, finally, it may sport a textual description
        that can be any string.

        '''
        try:
            logger.info('connecting to database {}'.format(sqlite_file))
            self._db = apsw.Connection(sqlite_file)

        except apsw.Error, e:
            logger.error('first attempt to open {} failed ({})'.
                         format(sqlite_file, e))

            dirs = os.path.dirname(sqlite_file)
            logger.debug('creating directory hierarchy {}'.format(dirs))
            try:
                os.makedirs(dirs)
            except os .error, e:
                logger.error('unable to create directory hierarchy {} ({})'.
                             format(dirs, e))
                raise connection_error(sqlite_file, e)

            try:
                logger.info('second attempt to open {}'.format(sqlite_file))
                self._db = apsw.Connection(sqlite_file)
            except apsw.Error, e:
                logger .error('second attempt to open {} also failed ({})'.
                              format  (sqlite_file, e))
                raise connection_error(sqlite_file, e)

        if (self.is_empty()):
            logger.info('{} is empty'.format(sqlite_file))
            self._init_morg_table()
        else:
            logger.info('{} has data'.format(sqlite_file))
            self._sanity_check(sqlite_file)

        self._init_item_table()
        self._init_property_tables(properties)

    # Create and populate watermark table
    def _init_morg_table(self):
        with self._db:
            logger.info('creating morg table')
            wm = watermark()
................................................................................
            columns = wm.columns()
            insert  = ('insert into morg ({}) values ({})'.
                       format(','.join(columns),
                              ','.join('?' * len(columns))))
            self.execute(insert, tuple(wm))

    # Try and confirm we're dealing with a Morg database
    def _sanity_check(self, sqlite_file):
        logger.info('checking morg table')
        try:
            morg = self.execute('select * from morg')
        except sql_error, e:
            raise sanity_check_error(sqlite_file,
                                     'unable to get data from morg table')

        n = len(morg)
        logger.debug('morg table has {} rows'.format(n))
        if (n < 1):
            raise sanity_check_error(sqlite_file, 'morg table has no data')
        if (n > 1):
            raise sanity_check_error(sqlite_file,
                                     'morg table has too much data')

        wm = watermark(morg[0])
        wm.dump()
        if (not wm):
            raise sanity_check_error(sqlite_file, 'magic phrase mismatch')
        logger.info('looks like this is a morg database')

    # Create item table if necessary
    def _init_item_table(self):
        logger.info('creating item table')
        create =  '''create table if not exists item(
                         id integer primary key autoincrement)'''
        self.execute(create)

    # Create and/or update the property tables
    def _init_property_tables(self, properties):
        logger.info('creating property table')
        create =  '''create table if not exists property(
................................................................................
        column constraint and result in a <tt>sql_error</tt>. Trying to
        use a zero-length string will result in a
        <tt>property_error</tt>.

        The property type can be one of the following (case-insensitive)
        strings:

        @li <tt> integer </tt>
        @li <tt> real    </tt>
        @li <tt> text    </tt>

        If applicable, you may specify the range of values a property may
        assume. For numeric properties, you may use the syntax
        <tt>min..max</tt> to specify the inclusive range
        <tt>[min, max]</tt>.

        All three property types may also take on discrete values. You
................................................................................
            select  = '''select id from property where(
                             name = (?) and type = (?))'''
            i, = self.execute(select, (n, t))[0]
            logger.debug('creating table property_{} for property "{}"'.
                         format(i, n))
            create  = ('''create table if not exists property_{}(
                              id integer primary key autoincrement,
                              item_id integer not null,
                              value {} not null {})'''.
                       format(i, t, range_constraint('value', t, r)))
            self.execute(create)

    def is_empty(self):
        '''Check if database has anything in it.

................................................................................
    Please note that the watermark system Morg uses is not foolproof. You
    can easily enough insert and populate such a table in an arbitrary
    SQLite file and "trick" Morg into using that file as its database. As
    a matter of fact, this class is designed specifically to facilitate
    such "Morgification" without requiring you to jump through
    unnecessary hoops.

    For example, assuming the <tt>morglib</tt> package is in your Python
    path, here is a quick Python program to generate a SQL script that
    you can then use to "Morgify" an SQLite file:

    @verbatim
        import morglib
        f = open('morgify.sql', 'w')
        f.write(morglib.database.watermark().sql())
................................................................................
            wm  = morglib.database.watermark()
            cur = db.cursor()
            cur.execute(wm.create())
            cur.execute('insert into morg values (?,?,?,?,?)', tuple(wm))
    @endverbatim

    After you run the above program, <tt>foo.sqlite</tt> will have the
    necessary magic to allow Morg to use it as an items database.

    To prevent Morg from using an SQLite file, all you have to do is
    delete the <tt>morg</tt> table from it (or just mangle its contents
    so that the sanity check fails). <tt>morglib</tt>, however, does not
    provide a straightforward means of demorgification of a database. For
    that, you'll have to resort to the SQLite command-line.

................................................................................
        '''Construct a watermark object.

        @param metadata (tuple or dict) Watermark table's data.

        The <tt>morg</tt> table contains exactly one row, which has the
        following columns:

        @li <tt> schema_version </tt>
        @li <tt> timestamp      </tt>
        @li <tt> hostname       </tt>
        @li <tt> username       </tt>
        @li <tt> magic_phrase   </tt>

        The schema version is an integer that helps track the structure
        of the database. It can be used, for example, to upgrade older
        data models to more current ones.

        The time stamp records the creation time (GMT) of the database in
        the format <tt>yyyy-mm-dd HH:MM:SS</tt>.
................................................................................
    '''Parse range specification and return column constraint clause.

    @param  column_name (string) Name of column that needs a constraint.
    @param  column_type (string) Type of column that needs a constraint.
    @param  range_spec  (string or None) Range spec to be translated to SQL.
    @return String containing SQL constraint clause for column.

    Each item property has a name, a type, and, optionally, a range. Morg
    allows users to specify ranges using double dots to indicate min and
    max values for a numeric property and commas to list discrete values
    for any supported type.

    This function converts the range specification strings described
    above into a valid SQL constraint clause for the named column.

Changes to py/morglib/edit.py.

1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  edit.py
# @brief Command to edit tasks and properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#---------------------------- EDIT COMMAND ------------------------------

class edit(command.base):
    '''Command for editing tasks and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################


|







 







|







1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  edit.py
# @brief Command to edit items and properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#---------------------------- EDIT COMMAND ------------------------------

class edit(command.base):
    '''Command for editing items and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################

Changes to py/morglib/exit.py.

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

class exit(command.base):
    '''Command to exit Morg.'''
    def __call__(self, argv, db):
        '''Execute exit command.

        @param argv (string list) Exit command-line.
        @param db (morglib.database) Tasks database.

        '''
        if (len(argv) > 1 and argv[1] in ['-h', '--help']):
            print(_help())
        else:
            sys.exit(0)








|







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

class exit(command.base):
    '''Command to exit Morg.'''
    def __call__(self, argv, db):
        '''Execute exit command.

        @param argv (string list) Exit command-line.
        @param db (morglib.database) Items database.

        '''
        if (len(argv) > 1 and argv[1] in ['-h', '--help']):
            print(_help())
        else:
            sys.exit(0)

Changes to py/morglib/help.py.

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    def __init__(self):
        self._help = None

    def __call__(self, argv, db):
        '''Execute help command.

        @param argv (string list) Help command-line.
        @param db (morglib.database) Tasks database.

        Without any parameters, prints the list of available commands and
        some general comments.

        With a parameter, executes the specified command, passing it the
        --help option. Thus, all commands should implement --help to be
        able to print something useful when the user asks for help.







|







48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    def __init__(self):
        self._help = None

    def __call__(self, argv, db):
        '''Execute help command.

        @param argv (string list) Help command-line.
        @param db (morglib.database) Items database.

        Without any parameters, prints the list of available commands and
        some general comments.

        With a parameter, executes the specified command, passing it the
        --help option. Thus, all commands should implement --help to be
        able to print something useful when the user asks for help.

Changes to py/morglib/ls.py.

1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  ls.py
# @brief Command to list tasks and properties matching search parameters.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#--------------------------- SEARCH COMMAND -----------------------------

class ls(command.base):
    '''Command for listing tasks and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################


|







 







|







1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  ls.py
# @brief Command to list items and properties matching search parameters.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#--------------------------- SEARCH COMMAND -----------------------------

class ls(command.base):
    '''Command for listing items and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################

Changes to py/morglib/new.py.

1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  new.py
# @brief Command to create new tasks and properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#--------------------------- CREATE COMMAND -----------------------------

class new(command.base):
    '''Command for creating new tasks and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################


|







 







|







1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  new.py
# @brief Command to create new items and properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#--------------------------- CREATE COMMAND -----------------------------

class new(command.base):
    '''Command for creating new items and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################

Changes to py/morglib/properties.py.

1
2
3
4
5
6
7
8
9
10
..
26
27
28
29
30
31
32
33

34
35

36
37
38
39
40
41
42
43
44
45



46
47
48
49
50
51
52
53
#
# @file  properties.py
# @brief Morg's predefined task properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#
# You should have received a copy of the GNU General Public License
# along with Morg. If not, see <http://www.gnu.org/licenses/>.
#

#------------------------ PREDEFINED PROPERTIES -------------------------

# These are Morg's predefined properties; but users can augment this list

# or even override it wholesale.
defaults = {'name'        : ('text'   , None   ),

            'subtask_id'  : ('integer', None   ),
            'parent_id'   : ('integer', None   ),
            'day'         : ('integer', '1..7' ),
            'date'        : ('integer', '1..31'),
            'month'       : ('integer', '1..12'),
            'year'        : ('integer', None   ),
            'start_hour'  : ('integer', '0..23'),
            'start_minute': ('integer', '0..59'),
            'end_hour'    : ('integer', '0..23'),
            'end_minute'  : ('integer', '0..59'),



            'notes'       : ('text'   , None   ),}

#------------------------------------------------------------------------

##############################################
# Editor config:                             #
##############################################
# Local Variables:                           #


|







 







|
>
|
|
>
|
|
|
|
|
|
|
|
|
|
>
>
>
|







1
2
3
4
5
6
7
8
9
10
..
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#
# @file  properties.py
# @brief Morg's predefined item properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#
# You should have received a copy of the GNU General Public License
# along with Morg. If not, see <http://www.gnu.org/licenses/>.
#

#------------------------ PREDEFINED PROPERTIES -------------------------

# These are Morg's predefined properties, which are geared towards
# managing TODO and shopping lists. But users can augment this list or
# even override it wholesale.
defaults = {'name'        : ('text'   , None       ),
            'type'        : ('text'   , 'task,shop'),
            'subitem_id'  : ('integer', None       ),
            'parent_id'   : ('integer', None       ),
            'day'         : ('integer', '1..7'     ),
            'date'        : ('integer', '1..31'    ),
            'month'       : ('integer', '1..12'    ),
            'year'        : ('integer', None       ),
            'start_hour'  : ('integer', '0..23'    ),
            'start_minute': ('integer', '0..59'    ),
            'end_hour'    : ('integer', '0..23'    ),
            'end_minute'  : ('integer', '0..59'    ),
            'store'       : ('text'   , None       ),
            'price'       : ('real'   , None       ),
            'quantity'    : ('integer', None       ),
            'notes'       : ('text'   , None       ),}

#------------------------------------------------------------------------

##############################################
# Editor config:                             #
##############################################
# Local Variables:                           #

Changes to py/morglib/rm.py.

1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  rm.py
# @brief Command to deleting tasks and properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#--------------------------- DELETE COMMAND -----------------------------

class rm(command.base):
    '''Command for deleting tasks and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################


|







 







|







1
2
3
4
5
6
7
8
9
10
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#
# @file  rm.py
# @brief Command to deleting items and properties.
#

#
# Copyright (C) 2014 The Morg Project Developers
#
# See wiki/copyright.wiki in the top-level directory of the Morg source
# distribution for the full list of authors who have contributed to this
................................................................................
#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

#--------------------------- DELETE COMMAND -----------------------------

class rm(command.base):
    '''Command for deleting items and properties.

    '''
    pass

#------------------------------------------------------------------------

##############################################

Changes to wiki/home.wiki.

1
2
3
4
5


6
7
8




9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<title>Home</title>

<h1>About Morg</h1>

Morg is a TODO list manager with the following goals:



  *  <b>Hierarchical Task List</b>: Morg allows tasks to be arranged
     hierarchically, i.e., a task may have subtasks, which, in turn, may




     have further subtasks to any level of nesting.

  *  <b>Arbitrary Tags</b>: Each task may be associated with any number
     of properties such as name, time slot, urgency, importance, and so
     on. Properties may be repeated; thus, for example, a task may, if
     you like, have more than one name, more than one subtask, and even
     more than one parent task.

  *  <b>Versatility</b>: Morg does not adhere to any particular time
     management methodology (or fad). Rather, it implements a sound data
     model and provides functions to manipulate that data and then gets
     out of your way, which means you can adapt it your particular needs
     and style.

................................................................................
     don't want hierarchical lists, you can delete the properties that
     facilitate list nesting.

  *  <b>Portability</b>: Morg stores its data in an SQLite database. On
     laptops and desktops, it is written in Python and, so, will work
     wherever Python is available. It also has an Android app. And,
     finally, it supports syncing across devices, which means you can
     take your TODO list with you anywhere and have updates propagate
     without requiring a bunch of other tools.

  *  <b>Security</b>: Your TODO list can be intensely personal to you. No
     one else should have access to it without your consent. Thus, Morg
     has support for database encryption. Moreover, syncing is
     peer-to-peer, i.e., syncing between your phone and laptop will not
     involve a third-party server.

In short, Morg helps you stay on top of all the things you need to get
done before you end up in the morgue...

<h1>News</h1>



<h1>Documentation</h1>





|
>
>

<
<
>
>
>
>
|

|
|
|
|
|







 







|
|

|
|
|
|
|


|







1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
..
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<title>Home</title>

<h1>About Morg</h1>

Morg is a list manager that is geared toward helping you keep track of
the things you need to do (i.e., tasks list) and the things you need to
get (i.e., shopping lists).



Morg has the following design goals:

  *  <b>Hierarchical Lists</b>: Morg allows items to be arranged
     hierarchically, i.e., an item may have subitems, which, in turn, may
     have further subitems to any level of nesting.

  *  <b>Arbitrary Tags</b>: Each item may be associated with any number
     of properties such as name, time slot, priority, price, quantity,
     and so on. Properties may be repeated; thus, for example, an item
     may, if you like, have more than one name, more than one subitem,
     and even more than one parent item.

  *  <b>Versatility</b>: Morg does not adhere to any particular time
     management methodology (or fad). Rather, it implements a sound data
     model and provides functions to manipulate that data and then gets
     out of your way, which means you can adapt it your particular needs
     and style.

................................................................................
     don't want hierarchical lists, you can delete the properties that
     facilitate list nesting.

  *  <b>Portability</b>: Morg stores its data in an SQLite database. On
     laptops and desktops, it is written in Python and, so, will work
     wherever Python is available. It also has an Android app. And,
     finally, it supports syncing across devices, which means you can
     take your TODO and shopping lists with you anywhere and have updates
     propagate without requiring a bunch of other tools.

  *  <b>Security</b>: Your task and shopping list can be intensely
     personal to you. No one else should have access to these things
     without your consent. Thus, Morg has support for database
     encryption. Moreover, syncing is peer-to-peer, i.e., syncing between
     your phone and laptop will not involve a third-party server.

In short, Morg helps you stay on top of all the things you need to get
and/or get done before you end up in the morgue...

<h1>News</h1>



<h1>Documentation</h1>

Changes to wiki/schema.wiki.

1
2
3
4
5
6
7
8
9
10
..
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
..
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
142
143
144
145
146
147
148






149
150
151
152
153
154
155
156
...
200
201
202
203
204
205
206


















207
208
209
210
211
212
213
214
215
216
217
218
219
<title>Morg Database Schema</title>

This page describes the different tables that Morg uses in its tasks
database and the relationships between them.

<h1>morg</h1>

<center>
<table border="1">
  <tr>
................................................................................
The magic phrase is the SHA-1 ID computed by concatenating the other
columns along with a hard-coded string.

Although the above test is easily subverted, it does, nonetheless,
provide a reasonable sanity check that Morg is, in fact, using a valid
Morg database and not futzing around with something else.

<h1>Tasks</h1>

<h2>task</h2>

<center>
<table border="1">
  <tr> <td> id </td> </tr>
</table>
</center>

This table is simply a list of all the tasks in the database. Each task
has a corresponding table named <tt>task_NNN</tt>, where <tt>NNN</tt> is
the task ID.

<h2>task_NNN</h2>

<center>
<table border="1">
  <tr> <td> property_id </td> <td> value_id </td> </tr>
</table>
</center>

For each task, we record a collection of properties and their
corresponding values. The <tt>property_id</tt> refers to the row of the
<tt>property</tt> table. The <tt>value_id</tt> refers to the row of the
<tt>property_NNN</tt> table.

A task is allowed to have more than one instance of the same property.
Thus, for example, a task may have multiple names, subtasks, parent
tasks, etc.

<h1>Properties</h1>

<h2>property</h2>

<center>
<table border="1">
................................................................................
    <td> type (text) </td>
    <td> range (text) </td>
  </tr>
</table>
</center>

This table records the name, type, and range of each property that Morg
can associate with a task.

Property names are arbitrary strings assigned by users. Property types,
however, are restricted to one of the following (case-insensitive)
strings:

  *  integer
  *  real
  *  text

The range of a property is optional. If given, Morg will interpret the
range string as follows:

  *  Commas separate discrete allowed values (e.g., a property
     <tt>foo</tt> with type <tt>integer</tt> and range <tt>1,3,5,7</tt>
     can only take on those values).
  *  Double dots specify numeric ranges (e.g., a property <tt>foo</tt> of
     type <tt>real</tt> and range <tt>-1..+1</tt> will be allowed to take
     on values from -1 to +1).

<h2>property_NNN</h2>

<center>
<table border="1">
  <tr>
    <td> id </td>
    <td> task_id </td>
    <td> value (type from property:type) </td>
  </tr>
</table>
</center>

Similar to tasks, for each property, we will have a table
<tt>property_NNN</tt>, where <tt>NNN</tt> is the ID of the property
defined in the <tt>property</tt> table.

In a <tt>property_NNN</tt> table, we record the ID of the task to which
that particular instance of the property belongs along with its value.
The <tt>value</tt> column's type is the same as the one recorded in the
<tt>property</tt> table's <tt>type</tt> column for that property.

<h2>Default Properties</h2>

Morg allows users to define whatever properties they would like. However,
in a new database, for the sake of convenience and to be able to provide
some basic functionality out-of-the-box, Morg sets up the following
predefined properties:

<center>
................................................................................
    <td> </td>
    <td> name </td>
    <td> text </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>






    <td> subtask_id </td>
    <td> integer </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>
    <td> parent_id </td>
    <td> integer </td>
................................................................................
  </tr>
  <tr>
    <td> </td>
    <td> end_minute </td>
    <td> integer </td>
    <td> 0 .. 59 </td>
  </tr>


















  <tr>
    <td> </td>
    <td> notes </td>
    <td> text </td>
    <td> </td>
  </tr>
</table>
</center>

Users are not restricted to associating tasks with the above properties.
They may augment the above properties with their own. They may also
delete these defaults and define their own properties from scratch if
they so desire.


|







 







|

|







|
|
|

|







|




|
|
|







 







|












|

|
|

|







|
|




|



|




|







 







>
>
>
>
>
>
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













1
2
3
4
5
6
7
8
9
10
..
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
..
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<title>Morg Database Schema</title>

This page describes the different tables that Morg uses in its items
database and the relationships between them.

<h1>morg</h1>

<center>
<table border="1">
  <tr>
................................................................................
The magic phrase is the SHA-1 ID computed by concatenating the other
columns along with a hard-coded string.

Although the above test is easily subverted, it does, nonetheless,
provide a reasonable sanity check that Morg is, in fact, using a valid
Morg database and not futzing around with something else.

<h1>Items</h1>

<h2>item</h2>

<center>
<table border="1">
  <tr> <td> id </td> </tr>
</table>
</center>

This table is simply a list of all the items in the database. Each item
has a corresponding table named <tt>item_NNN</tt>, where <tt>NNN</tt> is
the item ID.

<h2>item_NNN</h2>

<center>
<table border="1">
  <tr> <td> property_id </td> <td> value_id </td> </tr>
</table>
</center>

For each item, we record a collection of properties and their
corresponding values. The <tt>property_id</tt> refers to the row of the
<tt>property</tt> table. The <tt>value_id</tt> refers to the row of the
<tt>property_NNN</tt> table.

An item is allowed to have more than one instance of the same property.
Thus, for example, an item may have multiple names, subitems, parent
items, etc.

<h1>Properties</h1>

<h2>property</h2>

<center>
<table border="1">
................................................................................
    <td> type (text) </td>
    <td> range (text) </td>
  </tr>
</table>
</center>

This table records the name, type, and range of each property that Morg
can associate with an item.

Property names are arbitrary strings assigned by users. Property types,
however, are restricted to one of the following (case-insensitive)
strings:

  *  integer
  *  real
  *  text

The range of a property is optional. If given, Morg will interpret the
range string as follows:

  *  Commas separate discrete allowed values, e.g., a property
     <tt>foo</tt> with type <tt>integer</tt> and range <tt>1,3,5,7</tt>
     can only take on those values.
  *  Double dots specify numeric ranges, e.g., a property <tt>foo</tt> of
     type <tt>real</tt> and range <tt>-1..+1</tt> will be allowed to take
     on values from -1 to +1 (inclusive).

<h2>property_NNN</h2>

<center>
<table border="1">
  <tr>
    <td> id </td>
    <td> item_id </td>
    <td> value </td>
  </tr>
</table>
</center>

Similar to items, for each property, we will have a table
<tt>property_NNN</tt>, where <tt>NNN</tt> is the ID of the property
defined in the <tt>property</tt> table.

In a <tt>property_NNN</tt> table, we record the ID of the item to which
that particular instance of the property belongs along with its value.
The <tt>value</tt> column's type is the same as the one recorded in the
<tt>property</tt> table's <tt>type</tt> column for that property.

<h2>Predefined Properties</h2>

Morg allows users to define whatever properties they would like. However,
in a new database, for the sake of convenience and to be able to provide
some basic functionality out-of-the-box, Morg sets up the following
predefined properties:

<center>
................................................................................
    <td> </td>
    <td> name </td>
    <td> text </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>
    <td> type </td>
    <td> text </td>
    <td> task, shop </td>
  </tr>
  <tr>
    <td> </td>
    <td> subitem_id </td>
    <td> integer </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>
    <td> parent_id </td>
    <td> integer </td>
................................................................................
  </tr>
  <tr>
    <td> </td>
    <td> end_minute </td>
    <td> integer </td>
    <td> 0 .. 59 </td>
  </tr>
  <tr>
    <td> </td>
    <td> store </td>
    <td> text </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>
    <td> price </td>
    <td> real </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>
    <td> quantity </td>
    <td> integer </td>
    <td> </td>
  </tr>
  <tr>
    <td> </td>
    <td> notes </td>
    <td> text </td>
    <td> </td>
  </tr>
</table>
</center>

Users are not restricted to associating tasks with the above properties.
They may augment the above properties with their own. They may also
delete these defaults and define their own properties from scratch if
they so desire.

Changes to wiki/todo.wiki.

20
21
22
23
24
25
26

27
28
29

30



  *  Implement sanity check on database initialization.
  *  Update database constructor doc string.
  *  Use the BETWEEN operator in property range constraints.
  *  Implement a watermark class to ease verification and "Morgification."
  *  The watermark table should be created and populated in a transaction.
  *  Update doc strings to reflect recent changes about the watermark.
  *  Pass database to all commands' <tt>__call__</tt> method.


<h2>PENDING</h2>


  *  Implement <tt>new</tt> command.










>



>
|
>
>
>
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  *  Implement sanity check on database initialization.
  *  Update database constructor doc string.
  *  Use the BETWEEN operator in property range constraints.
  *  Implement a watermark class to ease verification and "Morgification."
  *  The watermark table should be created and populated in a transaction.
  *  Update doc strings to reflect recent changes about the watermark.
  *  Pass database to all commands' <tt>__call__</tt> method.
  *  Generalize Morg so it isn't necessarily task-centric.

<h2>PENDING</h2>

  *  Implement <tt>morglib.database.add_item()</tt>.
  *  Implement <tt>new</tt> command-line processing.
  *  Hook up <tt>new</tt> command to <tt>add_item()</tt>.
  *  Hook up <tt>new</tt> command to <tt>add_property()</tt>.
  *  Add some user documentation.