From 3351cbe5eef797b5071c8bcdbb4bdd9a31cec5ae Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 22 Dec 2014 20:32:58 +0000 Subject: [PATCH] service.py: Fix CommandRemoteService handling of vectors. The CommandRemoteService class previously couldn't handle vector arguments at all, and in particular it dropped the FIELDS argument to `mkpwent' on the floor. It also dropped the PASSWORD argument, which was just stupid. Convert `_mkcmd' to handle all arguments as vectors, and fix the callers to wrap their scalar arguments in little vectors. Now we take the cross product of all of the arguments when substituting templates. --- service.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/service.py b/service.py index 786ab92..b1f170c 100644 --- a/service.py +++ b/service.py @@ -348,8 +348,14 @@ class CommandRemoteService (BasicRemoteService): containing `%' placeholders, as follows: `%u' the user's name + `%f' a user record field (list-valued) `%%' a single `%' character + If a template word contains placeholders for list-valued arguments, then + one output word is produced for each element of each list, with the + rightmost placeholder varying fastest. If any list is empty then no output + words are produced. + On success, the commands should print a line `OK' to standard output, and on any kind of anticipated failure, they should print `ERR' followed by an HTTP status code and a message; in either case, the program should exit @@ -379,15 +385,60 @@ class CommandRemoteService (BasicRemoteService): """Description of the remote service.""" return "`%s' command service (%s)" % (me.name, ' '.join(me._default)) - def _subst(me, c, map): - """Return the substitution for the placeholder `%C'.""" - return map.get(c, c) + def _mkcmd(me, cmd, argmap): + """ + Construct the command to be executed, by substituting placeholders. + + The ARGMAP is a dictionary mapping placeholder letters to lists of + arguments. These are substituted cartesian-product style into the + command words. + """ - def _mkcmd(me, cmd, map): - """Construct the command to be executed, by substituting placeholders.""" - if map is None: return cmd - return [me.R_PAT.sub(lambda m: me._subst(m.group(1), map), arg) - for arg in cmd] + ## No command map, so assume someone's already done the hard word. + if argmap is None: return cmd + + ## Start on building a list of arguments. + ww = [] + + ## Work through each template argument in turn... + for w in cmd: + + ## Firstly, build a list of lists. We'll then take the cartesian + ## product of these, and concatenate each of the results. + pc = [] + last = 0 + for m in me.R_PAT.finditer(w): + start, end = m.start(0), m.end(0) + if start > last: pc.append([w[last:start]]) + ch = m.group(1) + if ch == '%': + pc.append(['%']) + else: + try: pc.append(argmap[m.group(1)]) + except KeyError: raise U.ExpectedError, ( + 500, "Unknown placeholder `%%%s' in command `%s'" % (ch, cmd)) + last = end + if last < len(w): pc.append([w[last:]]) + + ## If any of the components is empty then there's nothing to do for + ## this word. + if not all(pc): continue + + ## Now do all the substitutions. + ii = len(pc)*[0] + while True: + ww.append(''.join(map(lambda v, i: v[i], pc, ii))) + i = len(ii) - 1 + while i >= 0: + ii[i] += 1 + if ii[i] < len(pc[i]): break + ii[i] = 0 + i -= 1 + else: + break + + ## And finally we're done. + return ww def _dispatch(me, func, op, args, input = None): """ @@ -395,13 +446,14 @@ class CommandRemoteService (BasicRemoteService): Invoke FUNC, which works like `_run', with appropriate arguments. The OP is a remote command name; ARGS is a sequence of (C, ARG) pairs, where C - is a placeholder character and ARG is a string value; INPUT is the text - to provide to the command on standard input. + is a placeholder character and ARG is a list of string values; INPUT is + the text to provide to the command on standard input. """ try: cmd = me._opmap[op] except KeyError: - cmd = me._default + [op] + [v for k, v in args] + cmd = me._default + [op] + reduce(lambda x, y: x + y, + [v for k, v in args], []) map = None else: map = dict(args) @@ -409,19 +461,21 @@ class CommandRemoteService (BasicRemoteService): def setpasswd(me, user, passwd): """Service protocol: set the USER's password to PASSWD.""" - me._dispatch(me._run_noout, 'set', [('u', user)], passwd + '\n') + me._dispatch(me._run_noout, 'set', [('u', [user])], + input = passwd + '\n') def clearpasswd(me, user): """Service protocol: clear the USER's password.""" - me._dispatch(me._run_noout, 'clear', [('u', user)]) + me._dispatch(me._run_noout, 'clear', [('u', [user])]) def mkpwent(me, user, passwd, fields): """Service protocol: create a record for USER.""" - me._dispatch(me._run_noout, 'mkpwent', [('u', user)]) + me._dispatch(me._run_noout, 'mkpwent', [('u', [user]), ('f', fields)], + input = passwd + '\n') def rmpwent(me, user): """Service protocol: delete the record for USER.""" - me._dispatch(me._run_noout, 'rmpwent', [('u', user)]) + me._dispatch(me._run_noout, 'rmpwent', [('u', [user])]) CONF.export('CommandRemoteService') -- 2.11.0