1# Copyright (C) 2008-2011 Dejan Muhamedagic <dmuhamedagic@suse.de>
2# Copyright (C) 2013 Kristoffer Gronlund <kgronlund@suse.com>
3# See COPYING for license information.
4
5import os
6import re
7import shlex
8from . import command
9from . import completers as compl
10from . import utils
11from . import config
12from . import userdir
13from . import options
14from .template import LoadTemplate
15from .cliformat import cli_format
16from .cibconfig import mkset_obj, cib_factory
17from .msg import common_err, common_warn
18from .msg import syntax_err, err_buf
19
20
21def check_transition(inp, state, possible_l):
22    if state not in possible_l:
23        common_err("input (%s) in wrong state %s" % (inp, state))
24        return False
25    return True
26
27
28def _unique_config_name(tmpl):
29    n = 0
30    while n < 99:
31        c = "%s-%d" % (tmpl, n)
32        if not os.path.isfile("%s/%s" % (userdir.CRMCONF_DIR, c)):
33            return c
34        n += 1
35    raise ValueError("Failed to generate unique configuration name")
36
37
38class Template(command.UI):
39    '''
40    Configuration templates.
41    '''
42    name = "template"
43
44    def __init__(self):
45        command.UI.__init__(self)
46        self.curr_conf = ''
47        self.init_dir()
48
49    @command.skill_level('administrator')
50    @command.completers_repeating(compl.null, compl.call(utils.listtemplates))
51    def do_new(self, context, name, *args):
52        "usage: new [<config>] <template> [<template> ...] [params name=value ...]"
53        if not utils.is_filename_sane(name):
54            return False
55        if os.path.isfile("%s/%s" % (userdir.CRMCONF_DIR, name)):
56            common_err("config %s exists; delete it first" % name)
57            return False
58        lt = LoadTemplate(name)
59        rc = True
60        mode = 0
61        params = {"id": name}
62        loaded_template = False
63        for s in args:
64            if mode == 0 and s == "params":
65                mode = 1
66            elif mode == 1:
67                a = s.split('=')
68                if len(a) != 2:
69                    syntax_err(args, context='new')
70                    rc = False
71                else:
72                    params[a[0]] = a[1]
73            elif lt.load_template(s):
74                loaded_template = True
75            else:
76                rc = False
77        if not loaded_template:
78            if name not in utils.listtemplates():
79                common_err("Expected template argument")
80                return False
81            tmpl = name
82            name = _unique_config_name(tmpl)
83            lt = LoadTemplate(name)
84            lt.load_template(tmpl)
85        if rc:
86            lt.post_process(params)
87        if not rc or not lt.write_config(name):
88            return False
89        self.curr_conf = name
90
91    @command.skill_level('administrator')
92    @command.completers(compl.call(utils.listconfigs))
93    def do_delete(self, context, name, force=''):
94        "usage: delete <config> [force]"
95        if force:
96            if force != "force" and force != "--force":
97                syntax_err((context.get_command_name(), force), context='delete')
98                return False
99        if not self.config_exists(name):
100            return False
101        if name == self.curr_conf:
102            if not force and not config.core.force and \
103                    not utils.ask("Do you really want to remove config %s which is in use?" %
104                                  self.curr_conf):
105                return False
106            else:
107                self.curr_conf = ''
108        os.remove("%s/%s" % (userdir.CRMCONF_DIR, name))
109
110    @command.skill_level('administrator')
111    @command.completers(compl.call(utils.listconfigs))
112    def do_load(self, context, name=''):
113        "usage: load [<config>]"
114        if not name:
115            self.curr_conf = ''
116            return True
117        if not self.config_exists(name):
118            return False
119        self.curr_conf = name
120
121    @command.skill_level('administrator')
122    @command.completers(compl.call(utils.listconfigs))
123    def do_edit(self, context, name=''):
124        "usage: edit [<config>]"
125        if not name and not self.curr_conf:
126            common_err("please load a config first")
127            return False
128        if name:
129            if not self.config_exists(name):
130                return False
131            utils.edit_file("%s/%s" % (userdir.CRMCONF_DIR, name))
132        else:
133            utils.edit_file("%s/%s" % (userdir.CRMCONF_DIR, self.curr_conf))
134
135    @command.completers(compl.call(utils.listconfigs))
136    def do_show(self, context, name=''):
137        "usage: show [<config>]"
138        if not name and not self.curr_conf:
139            common_err("please load a config first")
140            return False
141        if name:
142            if not self.config_exists(name):
143                return False
144            print(self.process(name))
145        else:
146            print(self.process())
147
148    @command.skill_level('administrator')
149    @command.completers(compl.join(compl.call(utils.listconfigs),
150                                   compl.choice(['replace', 'update'])),
151                        compl.call(utils.listconfigs))
152    def do_apply(self, context, *args):
153        "usage: apply [<method>] [<config>]"
154        method = "replace"
155        name = ''
156        if len(args) > 0:
157            i = 0
158            if args[0] in ("replace", "update"):
159                method = args[0]
160                i += 1
161            if len(args) > i:
162                name = args[i]
163        if not name and not self.curr_conf:
164            common_err("please load a config first")
165            return False
166        if name:
167            if not self.config_exists(name):
168                return False
169            s = self.process(name)
170        else:
171            s = self.process()
172        if not s:
173            return False
174        tmp = utils.str2tmp(s)
175        if not tmp:
176            return False
177        if method == "replace":
178            if options.interactive and cib_factory.has_cib_changed():
179                if not utils.ask("This operation will erase all changes. Do you want to proceed?"):
180                    return False
181            cib_factory.erase()
182        set_obj = mkset_obj()
183        rc = set_obj.import_file(method, tmp)
184        try:
185            os.unlink(tmp)
186        except:
187            pass
188        return rc
189
190    @command.completers(compl.choice(['configs', 'templates']))
191    def do_list(self, context, templates=''):
192        "usage: list [configs|templates]"
193        if templates == "templates":
194            utils.multicolumn(utils.listtemplates())
195        elif templates == "configs":
196            utils.multicolumn(utils.listconfigs())
197        else:
198            print("Templates:")
199            utils.multicolumn(utils.listtemplates())
200            print("\nConfigurations:")
201            utils.multicolumn(utils.listconfigs())
202
203    def init_dir(self):
204        '''Create the conf directory, link to templates'''
205        if not os.path.isdir(userdir.CRMCONF_DIR):
206            try:
207                os.makedirs(userdir.CRMCONF_DIR)
208            except os.error as msg:
209                common_err("makedirs: %s" % msg)
210
211    def get_depends(self, tmpl):
212        '''return a list of required templates'''
213        # Not used. May need it later.
214        try:
215            templatepath = os.path.join(config.path.sharedir, 'templates', tmpl)
216            tf = open(templatepath, "r")
217        except IOError as msg:
218            common_err("open: %s" % msg)
219            return
220        l = []
221        for s in tf:
222            a = s.split()
223            if len(a) >= 2 and a[0] == '%depends_on':
224                l += a[1:]
225        tf.close()
226        return l
227
228    def config_exists(self, name):
229        if not utils.is_filename_sane(name):
230            return False
231        if not os.path.isfile("%s/%s" % (userdir.CRMCONF_DIR, name)):
232            common_err("%s: no such config" % name)
233            return False
234        return True
235
236    def replace_params(self, s, user_data):
237        change = False
238        for i, word in enumerate(s):
239            for p in user_data:
240                # is parameter in the word?
241                pos = word.find('%' + p)
242                if pos < 0:
243                    continue
244                endpos = pos + len('%' + p)
245                # and it isn't part of another word?
246                if re.match("[A-Za-z0-9]", word[endpos:endpos+1]):
247                    continue
248                # if the value contains a space or
249                # it is a value of an attribute
250                # put quotes around it
251                if user_data[p].find(' ') >= 0 or word[pos-1:pos] == '=':
252                    v = '"' + user_data[p] + '"'
253                else:
254                    v = user_data[p]
255                word = word.replace('%' + p, v)
256                change = True  # we did replace something
257            if change:
258                s[i] = word
259        if 'opt' in s:
260            if not change:
261                s = []
262            else:
263                s.remove('opt')
264        return s
265
266    def generate(self, l, user_data):
267        '''replace parameters (user_data) and generate output
268        '''
269        l2 = []
270        for piece in l:
271            piece2 = []
272            for s in piece:
273                s = self.replace_params(s, user_data)
274                if s:
275                    piece2.append(' '.join(s))
276            if piece2:
277                l2.append(cli_format(piece2, break_lines=True))
278        return '\n'.join(l2)
279
280    def process(self, config=''):
281        '''Create a cli configuration from the current config'''
282        try:
283            f = open("%s/%s" % (userdir.CRMCONF_DIR, config or self.curr_conf), 'r')
284        except IOError as msg:
285            common_err("open: %s" % msg)
286            return ''
287        l = []
288        piece = []
289        user_data = {}
290        # states
291        START = 0
292        PFX = 1
293        DATA = 2
294        GENERATE = 3
295        state = START
296        err_buf.start_tmp_lineno()
297        rc = True
298        for inp in f:
299            inp = utils.to_ascii(inp)
300            err_buf.incr_lineno()
301            if inp.startswith('#'):
302                continue
303            inp = inp.strip()
304            try:
305                s = shlex.split(inp)
306            except ValueError as msg:
307                common_err(msg)
308                continue
309            while '\n' in s:
310                s.remove('\n')
311            if not s:
312                if state == GENERATE and piece:
313                    l.append(piece)
314                    piece = []
315            elif s[0] in ("%name", "%depends_on", "%suggests"):
316                continue
317            elif s[0] == "%pfx":
318                if check_transition(inp, state, (START, DATA)) and len(s) == 2:
319                    pfx = s[1]
320                    state = PFX
321            elif s[0] == "%required":
322                if check_transition(inp, state, (PFX,)):
323                    state = DATA
324                    data_reqd = True
325            elif s[0] == "%optional":
326                if check_transition(inp, state, (PFX, DATA)):
327                    state = DATA
328                    data_reqd = False
329            elif s[0] == "%%":
330                if state != DATA:
331                    common_warn("user data in wrong state %s" % state)
332                if len(s) < 2:
333                    common_warn("parameter name missing")
334                elif len(s) == 2:
335                    if data_reqd:
336                        common_err("required parameter %s not set" % s[1])
337                        rc = False
338                elif len(s) == 3:
339                    user_data["%s:%s" % (pfx, s[1])] = s[2]
340                else:
341                    common_err("%s: syntax error" % inp)
342            elif s[0] == "%generate":
343                if check_transition(inp, state, (DATA,)):
344                    state = GENERATE
345                    piece = []
346            elif state == GENERATE:
347                if s:
348                    piece.append(s)
349            else:
350                common_err("<%s> unexpected" % inp)
351        if piece:
352            l.append(piece)
353        err_buf.stop_tmp_lineno()
354        f.close()
355        if not rc:
356            return ''
357        return self.generate(l, user_data)
358
359
360# vim:ts=4:sw=4:et:
361