Check-in [a855b05bad]
Not logged in

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

Overview
Comment:Implemented command-line parsing for the new command.
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: a855b05badd237aa22d2d91cf39220cb1a700bb9
User & Date: mvnathan 2014-09-20 07:51:22.812
Context
2014-09-20
08:38
Disallow item creation without any accompanying properties. check-in: 49440f6e1f user: mvnathan tags: dev
07:51
Implemented command-line parsing for the new command. check-in: a855b05bad user: mvnathan tags: dev
00:15
Implemented morglib.database.add_item(). check-in: 0c1020da14 user: mvnathan tags: dev
Changes
Unified Diff Ignore Whitespace Patch
Changes to py/morglib/args.py.
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
    parser.add_argument('command', nargs = argparse.REMAINDER,
                        help = 'Morg command to run',
                        metavar = 'CMD')

    args = parser.parse_args(argv)
    return args

#---------------------------- DEBUG SUPPORT -----------------------------

def dump(args):
    '''Dump command-line args to debug log.

    @param args argparse.Namespace object returned by parse() function.

    Be sure to call this function after logging has been initialized with
    morglib.log.init(). Otherwise, expect trouble.

    '''
    for attr in dir(args):
        if (attr.startswith('_')):
            continue
        logger.debug('{} = {}'.format(attr, getattr(args, attr)))

#--------------------------- PRIVATE HELPERS ----------------------------

class argv_parser(argparse.ArgumentParser):
    '''Argument parser that raises an exception instead of exiting.

    When argparse.ArgumentParser encounters an error, it simply quits
    with an error message. This helper class changes that behaviour so
    that an exception is raised instead, which allows us to customize how







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|







108
109
110
111
112
113
114
















115
116
117
118
119
120
121
122
    parser.add_argument('command', nargs = argparse.REMAINDER,
                        help = 'Morg command to run',
                        metavar = 'CMD')

    args = parser.parse_args(argv)
    return args

















#--------------------------- HELPER CLASSES -----------------------------

class argv_parser(argparse.ArgumentParser):
    '''Argument parser that raises an exception instead of exiting.

    When argparse.ArgumentParser encounters an error, it simply quits
    with an error message. This helper class changes that behaviour so
    that an exception is raised instead, which allows us to customize how
149
150
151
152
153
154
155













































156
157
158
159
160
161
162
        this arguments parser raises an exception so that Morg's main
        function can react as it sees fit rather than being beholden to
        the argparse module's whims.

        '''
        raise args_error('{}'.format(msg))















































def _init_defaults():
    '''Initialize default values for various command-line options.

    @return Dictionary containing default values.

    This function returns a dictionary of strings to strings with the







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







133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
        this arguments parser raises an exception so that Morg's main
        function can react as it sees fit rather than being beholden to
        the argparse module's whims.

        '''
        raise args_error('{}'.format(msg))

class print_help(argparse.Action):
    '''An action to print help without exiting.

    By default, argparse.ArgumentParser exits after printing help.
    However, when Morg is in interactive mode, we don't want a command's
    --help option to end up quitting the application. This class helps
    accomplish that.

    To use it, a command's implementation should follow the argument
    parsing approach illustrated below:

    @verbatim
        import morglib.args as args

        def parse(argv):
            parser = args.argv_parser(add_help = False)
            parser.add_argument('-h', '--help',
                                action = args.print_help, nargs = 0)
            # etc.
    @endverbatim

    The above will disable argparse's built-in help processing and use
    this one instead.

    '''
    def __call__(self, parser, namespace, values, option_string = None):
        parser.print_help()

#---------------------------- DEBUG SUPPORT -----------------------------

def dump(args):
    '''Dump command-line args to debug log.

    @param args argparse.Namespace object returned by parse() function.

    Be sure to call this function after logging has been initialized with
    morglib.log.init(). Otherwise, expect trouble.

    '''
    for attr in dir(args):
        if (attr.startswith('_')):
            continue
        logger.debug('{} = {}'.format(attr, getattr(args, attr)))

#--------------------------- PRIVATE HELPERS ----------------------------

def _init_defaults():
    '''Initialize default values for various command-line options.

    @return Dictionary containing default values.

    This function returns a dictionary of strings to strings with the
Changes to py/morglib/new.py.
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
# along with Morg. If not, see <http://www.gnu.org/licenses/>.
#

#------------------------------- IMPORTS --------------------------------

# Morg
import command


# Standard library
import logging


#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

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

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






    '''


    pass





































































































































































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

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







>



>








|
>
>

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







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
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
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
# along with Morg. If not, see <http://www.gnu.org/licenses/>.
#

#------------------------------- IMPORTS --------------------------------

# Morg
import command
import args

# Standard library
import logging
import argparse

#---------------------------- MODULE LOGGER -----------------------------

logger = logging.getLogger(__name__)

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

class new(command.base):
    '''Command for creating new items and properties.'''
    def __call__(self, argv, db):
        '''Execute new command.

        @param argv (string list) Command-line arguments.
        @param db (morglib.database) Items database.

        '''
        arg = _parse(argv[1:])
        print(arg)

#------------------------ COMMAND-LINE PARSING --------------------------

def _parse(argv):
    fmt = argparse.RawDescriptionHelpFormatter
    parser = args.argv_parser(description     = _help_before_options(),
                              epilog          = _help_after__options(),
                              prog            = 'new',
                              add_help        = False,
                              formatter_class = fmt)

    parser.add_argument('-h', '--help',
                        action = args.print_help, nargs = 0,
                        help = 'print this help')

    parser.add_argument('-p', '--property',
                        help = 'add a property instead of a task',
                        metavar = 'NAME')
    parser.add_argument('-t', '--property-type',
                        default = 'text', type = _validate_property_type,
                        help = 'type of property to add (default: %(default)s)',
                        metavar = 'TYPE')
    parser.add_argument('-r', '--property-range',
                        help = 'range of values property may assume',
                        metavar = 'RANGE')

    parser.add_argument('properties', nargs = argparse.REMAINDER,
                        help = 'property specifications for new task',
                        metavar = 'PROPERTIES')

    return parser.parse_args(argv)

def _help_before_options():
    help_text = '''
Use the new command to add items or properties to the Morg database.
Without any options, this command will add a new item to the database,
using the positional arguments as property specifications for the item.

With the -p option, however, you can add a new property instead of a new
item. You can use -t and -r to specify the new property's type and range.
Positional arguments in this case will be ignored. Similarly, -t and -r
will be ignored without -p.
'''
    return help_text

def _help_after__options():
    help_text = '''
Here is an example showing how to add a new item (it uses some of Morg's
predefined properties):

    new name=test type=task status=wip notes='this is a test'

As you can see, properties are specified as name-value pairs with the
names and values being separated from each other by an equals sign.
Moreover, you should quote names and values that have spaces in them.

If you wish to surround the equals signs with spaces, you should enclose
the entire property specification in quotes. Here is an example:

    new 'odd property name = value with spaces'

If you supply incorrect types for various properties or exceed their
defined ranges, the database will reject them and not add the requested
item. For example, the predefined property day has a type of integer and
a range of [1,7] and the predefined property price has type real without
any range. Thus, assuming you haven't changed Morg's predefined defaults,
the following command is doomed to failure:

    new day=10 price=foo

You don't have to type out the property names in full. You can abbreviate
them to unique prefixes. For example, Morg's default configuration
includes two properties that begin with the letter 'n', viz., name and
notes. Thus, you could just type:

    new na=foo no='well this is just dandy'

However, trying the following will result in an error because Morg won't
be able to tell whether you mean 'n' for name or 'n' for notes:

    new n=oops

Text properties with defined ranges may also be abbreviated. To
illustrate, consider this command:

    n na='what ever' t=t stat=w no='cryptic but less to type!'

Assuming you're using Morg's default settings, the above would create a
new item with the following properties:

    name      what ever
    type      task
    status    wip
    notes     cryptic but less to type!

In the above command, the first 'n' stands for the new command. Since
Morg strives to support shorthand wherever possible, its commands can
also be abbreviated. The "t=t" construct specifies the property named
"type," which has type text and range (task,shop). Thus, the 't' before
the equals expands to "type" and the 't' after the equals to "task."

Similarly, "stat" expands to "status" and 'w' to "wip" (the other
supported values for the status property are "todo" and "done").

When you're adding a new property instead of a task, keep in mind that
Morg only supports integer, real, and text as the possible property
types. That is, the -t option will only accept one of the following
strings: integer, real, text.

For the sake of convenience, you may abbreviate the type strings to any
acceptable unique prefix. Since the three types don't share any common
initial substrings, you could get away with supplying just the first
character of the type as shown below:

    new -p foo -t i

That will create a new integer property named foo that can take on any
integral value supported by the version of SQLite installed on your
system.

To restrict properties to specific ranges, use the -r option as shown
below:

    new -p role -r president,glutton,buffoon

That defines a new text property named role that can take on the values
president, glutton, or buffoon.

As usual, if the property name and/or values have spaces in them, use
quotes:

    new -p 'Job Title' -r 'Vice President,Male Gigolo,One Hit Wonder'

Numeric property ranges can also use commas:

    new -p foo -t i 1,3,5,7,9

Additionally, for numeric types, you can use double dots to specify a min
and max values:

    new -p urgency -t r -r 1..-1

Note that we put the min value -1 at the right of the double dot. That's
because it would have confused the new command's command-line parser into
thinking of it as an option -1. But don't worry, Morg's database will do
the right thing when it sees the above range and flip the bounds around
as required.

Any other syntax for property ranges will result in an error.
'''
    return help_text

# Allow property types to be abbreviated and ensure they're restricted to
# the ones that Morg supports.
def _validate_property_type(optval):
    completions = filter(lambda s: s.startswith(optval),
                         ('integer', 'real', 'text'))
    n = len(completions)
    if (n < 1):
        raise args.args_error('{}: unknown property type'.format(optval))
    if (n > 1):
        msg = ('{}: ambiguous property type; can be: {}'.
               format(optval, ', '.join(completions)))
        raise args.args_error(msg)
    return completions[0]

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

##############################################
# Editor config:                             #
##############################################
# Local Variables:                           #
Changes to wiki/todo.wiki.
22
23
24
25
26
27
28

29
30
31
32
33
34
35
  *  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.
  *  Implement <tt>morglib.database.add_item()</tt>.


<h2>PENDING</h2>

  *  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.







>



<



22
23
24
25
26
27
28
29
30
31
32

33
34
35
  *  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.
  *  Implement <tt>morglib.database.add_item()</tt>.
  *  Implement <tt>new</tt> command-line processing.

<h2>PENDING</h2>


  *  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.