"""RQL client for cubicweb, connecting to instance using pyro
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
import os
import sys
from logilab.common import flatten
from logilab.common.cli import CLIHelper
from logilab.common.clcommands import BadCommandUsage, pop_arg, register_commands
from cubicweb.toolsutils import CONNECT_OPTIONS, Command
# result formatter ############################################################
PAGER = os.environ.get('PAGER', 'less')
def pager_format_results(writer, layout):
"""pipe results to a pager like more or less"""
(r, w) = os.pipe()
pid = os.fork()
if pid == 0:
os.dup2(r, 0)
os.close(r)
os.close(w)
if PAGER == 'less':
os.execlp(PAGER, PAGER, '-r')
else:
os.execlp(PAGER, PAGER)
sys.exit(0)
stream = os.fdopen(w, "w")
os.close(r)
try:
format_results(writer, layout, stream)
finally:
stream.close()
os.waitpid(pid, 0)
def izip2(list1, list2):
for i in xrange(len(list1)):
yield list1[i] + tuple(list2[i])
def format_results(writer, layout, stream=sys.stdout):
"""format result as text into the given file like object"""
writer.format(layout, stream)
try:
encoding = sys.stdout.encoding
except AttributeError: # python < 2.3
encoding = 'UTF-8'
def to_string(value, encoding=encoding):
"""used to converte arbitrary values to encoded string"""
if isinstance(value, unicode):
return value.encode(encoding, 'replace')
return str(value)
# command line querier ########################################################
class RQLCli(CLIHelper):
"""Interactive command line client for CubicWeb, allowing user to execute
arbitrary RQL queries and to fetch schema information
"""
# commands are prefixed by ":"
CMD_PREFIX = ':'
# map commands to folders
CLIHelper.CMD_MAP.update({
'connect' : "CubicWeb",
'schema' : "CubicWeb",
'description' : "CubicWeb",
'commit' : "CubicWeb",
'rollback' : "CubicWeb",
'autocommit' : "Others",
'debug' : "Others",
})
def __init__(self, instance=None, user=None, password=None,
host=None, debug=0):
CLIHelper.__init__(self, os.path.join(os.environ["HOME"], ".erqlhist"))
self.cnx = None
self.cursor = None
# XXX give a Request like object, not None
from cubicweb.schemaviewer import SchemaViewer
self.schema_viewer = SchemaViewer(None, encoding=encoding)
from logilab.common.ureports import TextWriter
self.writer = TextWriter()
self.autocommit = False
self._last_result = None
self._previous_lines = []
if instance is not None:
self.do_connect(instance, user, password, host)
self.do_debug(debug)
def do_connect(self, instance, user=None, password=None, host=None):
"""connect to an cubicweb instance"""
from cubicweb.dbapi import connect
if user is None:
user = raw_input('login: ')
if password is None:
from getpass import getpass
password = getpass('password: ')
if self.cnx is not None:
self.cnx.close()
self.cnx = connect(login=user, password=password, host=host,
database=instance)
self.schema = self.cnx.get_schema()
self.cursor = self.cnx.cursor()
# add entities types to the completion commands
self._completer.list = (self.commands.keys() +
self.schema.entities() + ['Any'])
print _('You are now connected to %s') % instance
help_do_connect = ('connect', "connect <instance> [<user> [<password> [<host>]]]",
_(do_connect.__doc__))
def do_debug(self, debug=1):
"""set debug level"""
self._debug = debug
if debug:
self._format = format_results
else:
self._format = pager_format_results
if self._debug:
print _('Debug level set to %s'%debug)
help_do_debug = ('debug', "debug [debug_level]", _(do_debug.__doc__))
def do_description(self):
"""display the description of the latest result"""
if self.rset.description is None:
print _('No query has been executed')
else:
print '\n'.join([', '.join(line_desc)
for line_desc in self.rset.description])
help_do_description = ('description', "description", _(do_description.__doc__))
def do_schema(self, name=None):
"""display information about the instance schema """
if self.cnx is None:
print _('You are not connected to an instance !')
return
done = None
if name is None:
# display the full schema
self.display_schema(self.schema)
done = 1
else:
if self.schema.has_entity(name):
self.display_schema(self.schema.eschema(name))
done = 1
if self.schema.has_relation(name):
self.display_schema(self.schema.rschema(name))
done = 1
if done is None:
print _('Unable to find anything named "%s" in the schema !') % name
help_do_schema = ('schema', "schema [keyword]", _(do_schema.__doc__))
def do_commit(self):
"""commit the current transaction"""
self.cnx.commit()
help_do_commit = ('commit', "commit", _(do_commit.__doc__))
def do_rollback(self):
"""rollback the current transaction"""
self.cnx.rollback()
help_do_rollback = ('rollback', "rollback", _(do_rollback.__doc__))
def do_autocommit(self):
"""toggle autocommit mode"""
self.autocommit = not self.autocommit
help_do_autocommit = ('autocommit', "autocommit", _(do_autocommit.__doc__))
def handle_line(self, stripped_line):
"""handle non command line :
if the query is complete, executes it and displays results (if any)
else, stores the query line and waits for the suite
"""
if self.cnx is None:
print _('You are not connected to an instance !')
return
# append line to buffer
self._previous_lines.append(stripped_line)
# query are ended by a ';'
if stripped_line[-1] != ';':
return
# extract query from the buffer and flush it
query = '\n'.join(self._previous_lines)
self._previous_lines = []
# search results
try:
self.rset = rset = self.cursor.execute(query)
except:
if self.autocommit:
self.cnx.rollback()
raise
else:
if self.autocommit:
self.cnx.commit()
self.handle_result(rset)
def handle_result(self, rset):
"""display query results if any"""
if not rset:
print _('No result matching query')
else:
from logilab.common.ureports import Table
children = flatten(izip2(rset.description, rset.rows), to_string)
layout = Table(cols=2*len(rset.rows[0]), children=children, cheaders=1)
self._format(self.writer, layout)
print _('%s results matching query') % rset.rowcount
def display_schema(self, schema):
"""display a schema object"""
attr = schema.__class__.__name__.lower().replace('cubicweb', '')
layout = getattr(self.schema_viewer, 'visit_%s' % attr)(schema)
self._format(self.writer, layout)
class CubicWebClientCommand(Command):
"""A command line querier for CubicWeb, using the Relation Query Language.
<instance>
identifier of the instance to connect to
"""
name = 'client'
arguments = '<instance>'
options = CONNECT_OPTIONS + (
("verbose",
{'short': 'v', 'type' : 'int', 'metavar': '<level>',
'default': 0,
'help': 'ask confirmation to continue after an error.',
}),
("batch",
{'short': 'b', 'type' : 'string', 'metavar': '<file>',
'help': 'file containing a batch of RQL statements to execute.',
}),
)
def run(self, args):
"""run the command with its specific arguments"""
appid = pop_arg(args, expected_size_after=None)
batch_stream = None
if args:
if len(args) == 1 and args[0] == '-':
batch_stream = sys.stdin
else:
raise BadCommandUsage('too many arguments')
if self.config.batch:
batch_stream = open(self.config.batch)
cli = RQLCli(appid, self.config.user, self.config.password,
self.config.host, self.config.debug)
if batch_stream:
cli.autocommit = True
for line in batch_stream:
line = line.strip()
if not line:
continue
print '>>>', line
cli.handle_line(line)
else:
cli.run()
register_commands((CubicWebClientCommand,))