#!/usr/bin/python3

'''
virsh-xpath, version 0.1

Copyright 2012 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301 USA.

Syntax:
  virsh-xpath --guest NAME PARAMETERS [PARAMETERS ...]

PARAMETERS syntax:
  "xpath selector" [--add element --attr atribute=value \\
                   |--delete \\
                   |--remove attribute [--remove attribute ...] \\
                   |[--update] --attr attribute=value \\
                   |--stdin \\
                   |--dry-run]

Explanation:
  --stdin	read more commands from stdin
  --dry-run	do not change anything, just print out changed output
  --query	only print query results and exist, do not modify document

Examples:
  # Query boot options:
  --guest NAME --query //os/boot
  # Add an element with attributes:
  --guest NAME //os --add bios --attr useserial=yes
  # Update attribute of an element:
  --guest NAME //devices/disk/driver --update --attr cache=none
  # Remove attributes:
  --guest NAME //disk/driver --remove cache --remove type
  # Delete element:
  --guest NAME '//os/boot[@dev="hd"]' --delete

To apply multiple commands at once, use --stdin parameter and send
options from stdin. Example:
  virsh-xpath --guest NAME --stdin << EOF
    //os --add bios --attr useserial=yes
    //devices/disk/driver --update --attr cache=none
  EOF
'''

import sys, os, getopt, libxml2

def guests(opts):
    for key, value in opts:
      if key=='--guest' or key=='-G':
        yield value

def split_commands(args):
    ret = []
    for arg in args:
      if arg.startswith("/"):
        yield ret
        ret = [arg]
      elif arg=="--stdin":
        if ret:
          yield ret
          ret = []
        for line in sys.stdin.readlines():
          yield line.strip().split(" ")
      else:
        ret.append(arg)
    if ret:
      yield ret

def apply_opts(e, opts):
    for key, value in opts:
      if key=='--attribute' or key=='-t':
        k, v = value.split("=", 1)
        if v is None:
          e.unsetProp(k)
        else:
          e.setProp(k, v)
    return e

def get_opts(opts):
    ret = []
    for key, value in opts:
      if key=='--attribute' or key=='-t':
        k, v = value.split("=", 1)
        ret.append('%s="%s"' % (k, v))
    return ' '.join(ret)

if __name__ == "__main__":
  commands = list(split_commands(sys.argv[1:]))
  try:
    mopts, margs = getopt.gnu_getopt(commands[0],
      'G:hQ',
      ['guest=', 'capabilities', 'help', 'stdin', 'dry-run', 'query']
    )
    mparams = dict(mopts)
    if '--help' in mparams or '-h' in mparams:
      print(__doc__)
      sys.exit(0)
  except getopt.GetoptError as err:
    print("Error:", str(err))
    sys.exit(1)

  if '--capabilities' in mparams:
    source = os.popen('virsh capabilities').read()
    doc = libxml2.parseDoc(source)
    ctx = doc.xpathNewContext()
    query_args = ' '.join(sys.argv[2:])
    elements = ctx.xpathEval(query_args)
    for element in elements:
      print(element)
    
  for guest in guests(mopts):
    source = os.popen('virsh dumpxml %s' % guest).read()
    doc = libxml2.parseDoc(source)
    ctx = doc.xpathNewContext()

    for argv in commands[1:]:
      try:
        opts, args = getopt.gnu_getopt(argv,
          'a:dr:ut:',
          ['add=', 'delete', 'remove=', 'update', 'attribute=']
        )
        params = dict(opts)
      except getopt.GetoptError as err:
        print("Error:", str(err))
        sys.exit(1)

      query_args = ' '.join(args)
      elements = ctx.xpathEval(query_args)
      if '--add' in params or '-a' in params:
        if '-a' in params:
          node = params.get('-a')
        elif '--add' in params:
          node = params.get('--add')
        #e = pq("<%s/>\n" % add)
        e = libxml2.newNode(node)
        apply_opts(e, opts)
        for element in elements:
          element.addChild(e)
      elif '--delete' in params or '-d' in params:
        for element in elements:
          element.unlinkNode()
      elif '--remove' in params or '-r' in params:
        for key, value in opts:
          if key=='-r' or key=='--remove':
            for element in elements:
              element.unsetProp(value)
      elif opts:
        # '--update' in params or '-u' in params or "no params":
        for element in elements:
          apply_opts(element, opts)
      else:
        # only print query
        for element in elements:
          print(element)

    if '--query' not in mparams and '-Q' not in mparams:
      if '--dry-run' in mparams:
        print(doc.getRootElement())
      else:
        f = os.popen('EDITOR="cat >" virsh edit %s' % guest, 'w')
        f.write(str(doc.getRootElement()))
        f.close()

    doc.freeDoc()
    ctx.xpathFreeContext()
