1#!/usr/bin/env python
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this file,
4# You can obtain one at http://mozilla.org/MPL/2.0/.
5
6"""
7Mozilla universal manifest parser
8"""
9
10from optparse import OptionParser
11import os
12import sys
13
14from .manifestparser import (
15    convert,
16    ManifestParser,
17)
18
19
20class ParserError(Exception):
21    """error for exceptions while parsing the command line"""
22
23
24def parse_args(_args):
25    """
26    parse and return:
27    --keys=value (or --key value)
28    -tags
29    args
30    """
31
32    # return values
33    _dict = {}
34    tags = []
35    args = []
36
37    # parse the arguments
38    key = None
39    for arg in _args:
40        if arg.startswith('---'):
41            raise ParserError("arguments should start with '-' or '--' only")
42        elif arg.startswith('--'):
43            if key:
44                raise ParserError("Key %s still open" % key)
45            key = arg[2:]
46            if '=' in key:
47                key, value = key.split('=', 1)
48                _dict[key] = value
49                key = None
50                continue
51        elif arg.startswith('-'):
52            if key:
53                raise ParserError("Key %s still open" % key)
54            tags.append(arg[1:])
55            continue
56        else:
57            if key:
58                _dict[key] = arg
59                continue
60            args.append(arg)
61
62    # return values
63    return (_dict, tags, args)
64
65
66class CLICommand(object):
67    usage = '%prog [options] command'
68
69    def __init__(self, parser):
70        self._parser = parser  # master parser
71
72    def parser(self):
73        return OptionParser(usage=self.usage, description=self.__doc__,
74                            add_help_option=False)
75
76
77class Copy(CLICommand):
78    usage = '%prog [options] copy manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
79
80    def __call__(self, options, args):
81        # parse the arguments
82        try:
83            kwargs, tags, args = parse_args(args)
84        except ParserError, e:
85            self._parser.error(e.message)
86
87        # make sure we have some manifests, otherwise it will
88        # be quite boring
89        if not len(args) == 2:
90            HelpCLI(self._parser)(options, ['copy'])
91            return
92
93        # read the manifests
94        # TODO: should probably ensure these exist here
95        manifests = ManifestParser()
96        manifests.read(args[0])
97
98        # print the resultant query
99        manifests.copy(args[1], None, *tags, **kwargs)
100
101
102class CreateCLI(CLICommand):
103    """
104    create a manifest from a list of directories
105    """
106    usage = '%prog [options] create directory <directory> <...>'
107
108    def parser(self):
109        parser = CLICommand.parser(self)
110        parser.add_option('-p', '--pattern', dest='pattern',
111                          help="glob pattern for files")
112        parser.add_option('-i', '--ignore', dest='ignore',
113                          default=[], action='append',
114                          help='directories to ignore')
115        parser.add_option('-w', '--in-place', dest='in_place',
116                          help='Write .ini files in place; filename to write to')
117        return parser
118
119    def __call__(self, _options, args):
120        parser = self.parser()
121        options, args = parser.parse_args(args)
122
123        # need some directories
124        if not len(args):
125            parser.print_usage()
126            return
127
128        # add the directories to the manifest
129        for arg in args:
130            assert os.path.exists(arg)
131            assert os.path.isdir(arg)
132            manifest = convert(args, pattern=options.pattern, ignore=options.ignore,
133                               write=options.in_place)
134        if manifest:
135            print manifest
136
137
138class WriteCLI(CLICommand):
139    """
140    write a manifest based on a query
141    """
142    usage = '%prog [options] write manifest <manifest> -tag1 -tag2 --key1=value1 --key2=value2 ...'
143
144    def __call__(self, options, args):
145
146        # parse the arguments
147        try:
148            kwargs, tags, args = parse_args(args)
149        except ParserError, e:
150            self._parser.error(e.message)
151
152        # make sure we have some manifests, otherwise it will
153        # be quite boring
154        if not args:
155            HelpCLI(self._parser)(options, ['write'])
156            return
157
158        # read the manifests
159        # TODO: should probably ensure these exist here
160        manifests = ManifestParser()
161        manifests.read(*args)
162
163        # print the resultant query
164        manifests.write(global_tags=tags, global_kwargs=kwargs)
165
166
167class HelpCLI(CLICommand):
168    """
169    get help on a command
170    """
171    usage = '%prog [options] help [command]'
172
173    def __call__(self, options, args):
174        if len(args) == 1 and args[0] in commands:
175            commands[args[0]](self._parser).parser().print_help()
176        else:
177            self._parser.print_help()
178            print '\nCommands:'
179            for command in sorted(commands):
180                print '  %s : %s' % (command, commands[command].__doc__.strip())
181
182
183class UpdateCLI(CLICommand):
184    """
185    update the tests as listed in a manifest from a directory
186    """
187    usage = '%prog [options] update manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
188
189    def __call__(self, options, args):
190        # parse the arguments
191        try:
192            kwargs, tags, args = parse_args(args)
193        except ParserError, e:
194            self._parser.error(e.message)
195
196        # make sure we have some manifests, otherwise it will
197        # be quite boring
198        if not len(args) == 2:
199            HelpCLI(self._parser)(options, ['update'])
200            return
201
202        # read the manifests
203        # TODO: should probably ensure these exist here
204        manifests = ManifestParser()
205        manifests.read(args[0])
206
207        # print the resultant query
208        manifests.update(args[1], None, *tags, **kwargs)
209
210
211# command -> class mapping
212commands = {'create': CreateCLI,
213            'help': HelpCLI,
214            'update': UpdateCLI,
215            'write': WriteCLI}
216
217
218def main(args=sys.argv[1:]):
219    """console_script entry point"""
220
221    # set up an option parser
222    usage = '%prog [options] [command] ...'
223    description = "%s. Use `help` to display commands" % __doc__.strip()
224    parser = OptionParser(usage=usage, description=description)
225    parser.add_option('-s', '--strict', dest='strict',
226                      action='store_true', default=False,
227                      help='adhere strictly to errors')
228    parser.disable_interspersed_args()
229
230    options, args = parser.parse_args(args)
231
232    if not args:
233        HelpCLI(parser)(options, args)
234        parser.exit()
235
236    # get the command
237    command = args[0]
238    if command not in commands:
239        parser.error("Command must be one of %s (you gave '%s')" %
240                     (', '.join(sorted(commands.keys())), command))
241
242    handler = commands[command](parser)
243    handler(options, args[1:])
244
245if __name__ == '__main__':
246    main()
247