diff mbox

[v2,2/4] scripts: qmp-shell: Expand support for QMP expressions

Message ID 1429719709-880-3-git-send-email-jsnow@redhat.com
State New
Headers show

Commit Message

John Snow April 22, 2015, 4:21 p.m. UTC
This includes support for [] expressions, single-quotes in
QMP expressions (which is not strictly a part of JSON), and
the ability to use "True" or "False" literals instead of
JSON's lowercased 'true' and 'false' literals.

qmp-shell currently allows you to describe values as
JSON expressions:
key={"key":{"key2":"val"}}

But it does not currently support arrays, which are needed
for serializing and deserializing transactions:
key=[{"type":"drive-backup","data":{...}}]

qmp-shell also only currently accepts doubly quoted strings
as-per JSON spec, but QMP allows single quotes.

Lastly, python allows you to utilize "True" or "False" as
boolean literals, but JSON expects "true" or "false". Expand
qmp-shell to allow the user to type either, converting to the
correct type.

As a consequence of the above, the key=val parsing is also improved
to give better error messages if a key=val token is not provided.

CAVEAT: The parser is still extremely rudimentary and does not
expect to find spaces in {} nor [] expressions. This patch does
not improve this functionality.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 scripts/qmp/qmp-shell | 44 ++++++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 16 deletions(-)

Comments

Eric Blake April 22, 2015, 5:18 p.m. UTC | #1
On 04/22/2015 10:21 AM, John Snow wrote:
> This includes support for [] expressions, single-quotes in
> QMP expressions (which is not strictly a part of JSON), and
> the ability to use "True" or "False" literals instead of
> JSON's lowercased 'true' and 'false' literals.
> 
> qmp-shell currently allows you to describe values as
> JSON expressions:
> key={"key":{"key2":"val"}}
> 
> But it does not currently support arrays, which are needed
> for serializing and deserializing transactions:
> key=[{"type":"drive-backup","data":{...}}]
> 
> qmp-shell also only currently accepts doubly quoted strings
> as-per JSON spec, but QMP allows single quotes.
> 
> Lastly, python allows you to utilize "True" or "False" as
> boolean literals, but JSON expects "true" or "false". Expand
> qmp-shell to allow the user to type either, converting to the
> correct type.

Well, when easy to do.  See below for more discussion...

> 
> As a consequence of the above, the key=val parsing is also improved
> to give better error messages if a key=val token is not provided.
> 
> CAVEAT: The parser is still extremely rudimentary and does not
> expect to find spaces in {} nor [] expressions. This patch does
> not improve this functionality.

If someone cares about this, they can fix it later.  As I said in the v1
review, qmp-shell is mostly for debugging and testsuites, and we can
live with the caveats for now.

> 
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>  scripts/qmp/qmp-shell | 44 ++++++++++++++++++++++++++++----------------
>  1 file changed, 28 insertions(+), 16 deletions(-)

As far as I can tell, this makes qmp-shell strictly more useful, so I
have no qualms giving:

Reviewed-by: Eric Blake <eblake@redhat.com>

> 
> diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell
> index a9632ec..4cdcb6c 100755
> --- a/scripts/qmp/qmp-shell
> +++ b/scripts/qmp/qmp-shell
> @@ -88,23 +88,35 @@ class QMPShell(qmp.QEMUMonitorProtocol):
>          # clearing everything as it doesn't seem to matter
>          readline.set_completer_delims('')
>  
> +    def __parse_value(self, val):
> +        try:
> +            return int(val)
> +        except ValueError:
> +            pass
> +
> +        if val.lower() == 'true':
> +            return True
> +        if val.lower() == 'false':
> +            return False

...as promised earlier,

This allows param=tRuE (neither JSON nor Python), but that's a minor
price to pay to get both param=true (param=JSON style) and param=True
(param=Python style) to work.  Meanwhile...

> +        if val.startswith(('{', '[')):
> +            try:
> +                return json.loads(val)

This statement accepts param=JSON style, as in:
 param={ "foo":true }
but it rejects (as invalid JSON):
 param={ 'foo':true }
 param={ "foo":True }
 param={ 'foo':True }

> +            except ValueError:
> +                pass
> +            try:
> +                return ast.literal_eval(val)

And this statement accepts param=Python style, as in:
 param={ "foo":True }
 param={ 'foo':True }
but it rejects (as invalid Python):
 param={ "foo":true }
 param={ 'foo':true }

Of those four combinations, QMP accepts:
 param={ "foo":true }
 param={ 'foo':true }
while rejecting:
 param={ "foo":True }
 param={ 'foo':True }

So among the four combinations of single/double quoting vs upper/lower
casing of boolean literals, qmp-shell now accepts three variants, but we
have a case that QMP accepts but qmp-shell rejects (single quotes mixed
with lower-case true).

Not the end of the world (again, if someone wants all four cases to work
in qmp-shell and/or QMP proper, they can provide a patch later to
further enhance things).  But maybe worth expanding that CAVEAT in the
commit message to acknowledge the issues.

At any rate, doesn't affect my R-b given above.
Eric Blake April 22, 2015, 5:25 p.m. UTC | #2
On 04/22/2015 11:18 AM, Eric Blake wrote:
> On 04/22/2015 10:21 AM, John Snow wrote:
>> This includes support for [] expressions, single-quotes in
>> QMP expressions (which is not strictly a part of JSON), and
>> the ability to use "True" or "False" literals instead of
>> JSON's lowercased 'true' and 'false' literals.
>>

>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>  scripts/qmp/qmp-shell | 44 ++++++++++++++++++++++++++++----------------
>>  1 file changed, 28 insertions(+), 16 deletions(-)
> 
> As far as I can tell, this makes qmp-shell strictly more useful, so I
> have no qualms giving:
> 
> Reviewed-by: Eric Blake <eblake@redhat.com>


When combined with your one-line touchup, of course.
John Snow April 22, 2015, 5:33 p.m. UTC | #3
On 04/22/2015 01:18 PM, Eric Blake wrote:
> On 04/22/2015 10:21 AM, John Snow wrote:
>> This includes support for [] expressions, single-quotes in
>> QMP expressions (which is not strictly a part of JSON), and
>> the ability to use "True" or "False" literals instead of
>> JSON's lowercased 'true' and 'false' literals.
>>
>> qmp-shell currently allows you to describe values as
>> JSON expressions:
>> key={"key":{"key2":"val"}}
>>
>> But it does not currently support arrays, which are needed
>> for serializing and deserializing transactions:
>> key=[{"type":"drive-backup","data":{...}}]
>>
>> qmp-shell also only currently accepts doubly quoted strings
>> as-per JSON spec, but QMP allows single quotes.
>>
>> Lastly, python allows you to utilize "True" or "False" as
>> boolean literals, but JSON expects "true" or "false". Expand
>> qmp-shell to allow the user to type either, converting to the
>> correct type.
>
> Well, when easy to do.  See below for more discussion...
>
>>
>> As a consequence of the above, the key=val parsing is also improved
>> to give better error messages if a key=val token is not provided.
>>
>> CAVEAT: The parser is still extremely rudimentary and does not
>> expect to find spaces in {} nor [] expressions. This patch does
>> not improve this functionality.
>
> If someone cares about this, they can fix it later.  As I said in the v1
> review, qmp-shell is mostly for debugging and testsuites, and we can
> live with the caveats for now.
>
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   scripts/qmp/qmp-shell | 44 ++++++++++++++++++++++++++++----------------
>>   1 file changed, 28 insertions(+), 16 deletions(-)
>
> As far as I can tell, this makes qmp-shell strictly more useful, so I
> have no qualms giving:
>
> Reviewed-by: Eric Blake <eblake@redhat.com>
>
>>
>> diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell
>> index a9632ec..4cdcb6c 100755
>> --- a/scripts/qmp/qmp-shell
>> +++ b/scripts/qmp/qmp-shell
>> @@ -88,23 +88,35 @@ class QMPShell(qmp.QEMUMonitorProtocol):
>>           # clearing everything as it doesn't seem to matter
>>           readline.set_completer_delims('')
>>
>> +    def __parse_value(self, val):
>> +        try:
>> +            return int(val)
>> +        except ValueError:
>> +            pass
>> +
>> +        if val.lower() == 'true':
>> +            return True
>> +        if val.lower() == 'false':
>> +            return False
>
> ...as promised earlier,
>
> This allows param=tRuE (neither JSON nor Python), but that's a minor
> price to pay to get both param=true (param=JSON style) and param=True
> (param=Python style) to work.  Meanwhile...
>
>> +        if val.startswith(('{', '[')):
>> +            try:
>> +                return json.loads(val)
>
> This statement accepts param=JSON style, as in:
>   param={ "foo":true }
> but it rejects (as invalid JSON):
>   param={ 'foo':true }
>   param={ "foo":True }
>   param={ 'foo':True }
>
>> +            except ValueError:
>> +                pass
>> +            try:
>> +                return ast.literal_eval(val)
>
> And this statement accepts param=Python style, as in:
>   param={ "foo":True }
>   param={ 'foo':True }
> but it rejects (as invalid Python):
>   param={ "foo":true }
>   param={ 'foo':true }
>
> Of those four combinations, QMP accepts:
>   param={ "foo":true }
>   param={ 'foo':true }
> while rejecting:
>   param={ "foo":True }
>   param={ 'foo':True }
>
> So among the four combinations of single/double quoting vs upper/lower
> casing of boolean literals, qmp-shell now accepts three variants, but we
> have a case that QMP accepts but qmp-shell rejects (single quotes mixed
> with lower-case true).
>
> Not the end of the world (again, if someone wants all four cases to work
> in qmp-shell and/or QMP proper, they can provide a patch later to
> further enhance things).  But maybe worth expanding that CAVEAT in the
> commit message to acknowledge the issues.
>
> At any rate, doesn't affect my R-b given above.
>

Yes, unfortunately the object literals still must comply with some sort 
of standard, neither of which is truly QMP: They must either be Python 
or JSON.

key=val pairs can/will accept true/TRUE/tRuE etc as well, which I think 
is probably acceptable since that falls within the realm of "qmp-shell 
syntax" which doesn't necessarily have to maintain similarities to 
JSON/QMP/etc. I could probably go back and adjust it to just lowercase 
the first letter, but this is probably only ever going to be a typo 
(which we can unambiguously resolve) and the generated QMP (if -v is 
present) will of course appear correctly formatted.

GIQO: Garbage in, QMP out? :)

I will author an additional patch as an add-on a bit later that 
documents all the quirks to help improve the usability of this little tool.

Probably I'll also update the QMP banner to display a quick help message.

Thank you,
--John
diff mbox

Patch

diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell
index a9632ec..4cdcb6c 100755
--- a/scripts/qmp/qmp-shell
+++ b/scripts/qmp/qmp-shell
@@ -88,23 +88,35 @@  class QMPShell(qmp.QEMUMonitorProtocol):
         # clearing everything as it doesn't seem to matter
         readline.set_completer_delims('')
 
+    def __parse_value(self, val):
+        try:
+            return int(val)
+        except ValueError:
+            pass
+
+        if val.lower() == 'true':
+            return True
+        if val.lower() == 'false':
+            return False
+        if val.startswith(('{', '[')):
+            try:
+                return json.loads(val)
+            except ValueError:
+                pass
+            try:
+                return ast.literal_eval(val)
+            except SyntaxError:
+                pass
+        return val
+
     def __cli_expr(self, tokens, parent):
         for arg in tokens:
-            opt = arg.split('=')
-            try:
-                if(len(opt) > 2):
-                    opt[1] = '='.join(opt[1:])
-                value = int(opt[1])
-            except ValueError:
-                if opt[1] == 'true':
-                    value = True
-                elif opt[1] == 'false':
-                    value = False
-                elif opt[1].startswith('{'):
-                    value = json.loads(opt[1])
-                else:
-                    value = opt[1]
-            optpath = opt[0].split('.')
+            (key, _, val) = arg.partition('=')
+            if not val:
+                raise QMPShellError("Expected a key=value pair, got '%s'" % arg)
+
+            value = __parse_value(val)
+            optpath = key.split('.')
             curpath = []
             for p in optpath[:-1]:
                 curpath.append(p)
@@ -117,7 +129,7 @@  class QMPShell(qmp.QEMUMonitorProtocol):
                 if type(parent[optpath[-1]]) is dict:
                     raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
                 else:
-                    raise QMPShellError('Cannot set "%s" multiple times' % opt[0])
+                    raise QMPShellError('Cannot set "%s" multiple times' % key)
             parent[optpath[-1]] = value
 
     def __build_cmd(self, cmdline):