Mercurial > python-hglib
changeset 2:5fa34c3ac9a0
turn hglib into a module and expose open (previously connect) in its __init__.py
so common usage will now be:
import hglib
hglib.open(...)
also rename hglib.py to client.py
author | Idan Kamara <idankk86@gmail.com> |
---|---|
date | Sat, 23 Jul 2011 22:55:36 +0300 |
parents | bbd294291dd8 |
children | d7903b923217 |
files | hglib/__init__.py hglib/client.py hglib/hglib.py tests/test-hglib.py |
diffstat | 4 files changed, 317 insertions(+), 317 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hglib/__init__.py Sat Jul 23 22:55:36 2011 +0300 @@ -0,0 +1,9 @@ +from client import hgclient + +HGPATH = 'hg' + +def open(path=None, encoding=None, configs=None): + ''' starts a cmdserver for the given path (or for a repository found in the + cwd). HGENCODING is set to the given encoding. configs is a list of key, value, + similar to those passed to hg --config. ''' + return hgclient(path, encoding, configs)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hglib/client.py Sat Jul 23 22:55:36 2011 +0300 @@ -0,0 +1,303 @@ +import subprocess, os, struct, cStringIO, collections +import hglib, error, util + +class hgclient(object): + inputfmt = '>I' + outputfmt = '>cI' + outputfmtsize = struct.calcsize(outputfmt) + retfmt = '>i' + + # XXX fix this hack + _stylesdir = os.path.join(os.path.dirname(__file__), 'styles') + revstyle = ['--style', os.path.join(_stylesdir, 'rev.style')] + + revision = collections.namedtuple('revision', 'rev, node, tags, ' + 'branch, author, desc') + + def __init__(self, path, encoding, configs): + args = [hglib.HGPATH, 'serve', '--cmdserver', 'pipe'] + if path: + args += ['-R', path] + if configs: + args += ['--config'] + configs + env = dict(os.environ) + if encoding: + env['HGENCODING'] = encoding + + self.server = subprocess.Popen(args, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, env=env) + + self._readhello() + self._config = {} + + def _readhello(self): + """ read the hello message the server sends when started """ + ch, msg = self._readchannel() + assert ch == 'o' + + msg = msg.split('\n') + + self.capabilities = msg[0][len('capabilities: '):] + if not self.capabilities: + raise error.ResponseError("bad hello message: expected 'capabilities: '" + ", got %r" % msg[0]) + + self.capabilities = set(self.capabilities.split()) + + # at the very least the server should be able to run commands + assert 'runcommand' in self.capabilities + + self._encoding = msg[1][len('encoding: '):] + if not self._encoding: + raise error.ResponseError("bad hello message: expected 'encoding: '" + ", got %r" % msg[1]) + + def _readchannel(self): + data = self.server.stdout.read(hgclient.outputfmtsize) + if not data: + raise error.ServerError() + channel, length = struct.unpack(hgclient.outputfmt, data) + if channel in 'IL': + return channel, length + else: + return channel, self.server.stdout.read(length) + + def _parserevs(self, splitted): + ''' splitted is a list of fields according to our rev.style, where each 6 + fields compose one revision. ''' + return [self.revision._make(rev) for rev in util.grouper(6, splitted)] + + def _eatlines(self, s, n): + idx = 0 + for i in xrange(n): + idx = s.find('\n', idx) + 1 + + return s[idx:] + + def runcommand(self, args, inchannels, outchannels): + def writeblock(data): + self.server.stdin.write(struct.pack(self.inputfmt, len(data))) + self.server.stdin.write(data) + self.server.stdin.flush() + + if not self.server: + raise ValueError("server not connected") + + self.server.stdin.write('runcommand\n') + writeblock('\0'.join(args)) + + while True: + channel, data = self._readchannel() + + # input channels + if channel in inchannels: + writeblock(inchannels[channel](data)) + # output channels + elif channel in outchannels: + outchannels[channel](data) + # result channel, command finished + elif channel == 'r': + return struct.unpack(hgclient.retfmt, data)[0] + # a channel that we don't know and can't ignore + elif channel.isupper(): + raise error.ResponseError("unexpected data on required channel '%s'" + % channel) + # optional channel + else: + pass + + def outputruncommand(self, args, inchannels = {}, raiseonerror=True): + ''' run the command specified by args, returning (ret, output, error) ''' + out, err = cStringIO.StringIO(), cStringIO.StringIO() + outchannels = {'o' : out.write, 'e' : err.write} + ret = self.runcommand(args, inchannels, outchannels) + if ret and raiseonerror: + raise error.CommandError(args, ret, out.getvalue(), err.getvalue()) + return ret, out.getvalue(), err.getvalue() + + def close(self): + self.server.stdin.close() + self.server.wait() + ret = self.server.returncode + self.server = None + return ret + + @property + def encoding(self): + """ get the servers encoding """ + if not 'getencoding' in self.capabilities: + raise CapabilityError('getencoding') + + if not self._encoding: + self.server.stdin.write('getencoding\n') + self._encoding = self._readfromchannel('r') + + return self._encoding + + def config(self, refresh=False): + if not self._config or refresh: + self._config.clear() + + ret, out, err = self.outputruncommand(['showconfig']) + if ret: + raise error.CommandError(['showconfig'], ret, out, err) + + for entry in cStringIO.StringIO(out): + k, v = entry.rstrip().split('=', 1) + section, name = k.split('.', 1) + self._config.setdefault(section, {})[name] = v + + return self._config + + def status(self): + ret, out = self.outputruncommand(['status', '-0']) + + d = dict((c, []) for c in 'MARC!?I') + + for entry in out.split('\0'): + if entry: + t, f = entry.split(' ', 1) + d[t].append(f) + + return d + + def log(self, revrange=None): + args = ['log'] + self.revstyle + if revrange: + args.append('-r') + args += revrange + + out = self.outputruncommand(args)[1] + out = out.split('\0')[:-1] + + return self._parserevs(out) + + def incoming(self, revrange=None, path=None): + args = ['incoming'] + self.revstyle + if revrange: + args.append('-r') + args += revrange + + if path: + args += [path] + + ret, out, err = self.outputruncommand(args, raiseonerror=False) + if not ret: + out = self._eatlines(out, 2).split('\0')[:-1] + return self._parserevs(out) + elif ret == 1: + return [] + else: + raise error.CommandError(args, ret, out, err) + + def outgoing(self, revrange=None, path=None): + args = ['outgoing'] + self.revstyle + if revrange: + args.append('-r') + args += revrange + + if path: + args += [path] + + ret, out, err = self.outputruncommand(args, raiseonerror=False) + if not ret: + out = self._eatlines(out, 2).split('\0')[:-1] + return self._parserevs(out) + elif ret == 1: + return [] + else: + raise error.CommandError(args, ret, out, err) + + def commit(self, message, addremove=False): + args = ['commit', '-m', message] + + if addremove: + args += ['-A'] + + self.outputruncommand(args) + + # hope the tip hasn't changed since we committed + return self.tip() + + def import_(self, patch): + if isinstance(patch, str): + fp = open(patch) + else: + assert hasattr(patch, 'read') + assert hasattr(patch, 'readline') + + fp = patch + + try: + inchannels = {'I' : fp.read, 'L' : fp.readline} + self.outputruncommand(['import', '-'], inchannels) + finally: + if fp != patch: + fp.close() + + def root(self): + return self.outputruncommand(['root'])[1].rstrip() + + def clone(self, source='.', dest=None, branch=None, updaterev=None, + revrange=None): + args = ['clone'] + + if branch: + args += ['-b', branch] + if updaterev: + args += ['-u', updaterev] + if revrange: + args.append('-r') + args += revrange + args.append(source) + + if dest: + args.append(dest) + + self.outputruncommand(args) + + def tip(self): + out = self.outputruncommand(['tip'] + self.revstyle)[1] + out = out.split('\0') + + return self._parserevs(out)[0] + + def branch(self, name=None): + if not name: + return self.outputruncommand(['branch'])[1].rstrip() + + def branches(self): + out = self.outputruncommand(['branches'])[1] + branches = {} + for line in out.rstrip().split('\n'): + branch, revnode = line.split() + branches[branch] = self.log(revrange=[revnode.split(':')[0]])[0] + + return branches + + def paths(self, name=None): + if not name: + out = self.outputruncommand(['paths'])[1] + if not out: + return {} + + return dict([s.split(' = ') for s in out.rstrip().split('\n')]) + else: + args = ['paths', name] + ret, out, err = self.outputruncommand(args, raiseonerror=False) + if ret: + raise error.CommandError(args, ret, out, err) + return out.rstrip() + + def cat(self, files, rev=None, output=None): + args = ['cat'] + if rev: + args += ['-r', rev] + if output: + args += ['-o', output] + + args += files + ret, out, err = self.outputruncommand(args) + + if not output: + return out
--- a/hglib/hglib.py Sat Jul 23 22:54:23 2011 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,311 +0,0 @@ -import subprocess, os, struct, cStringIO, collections -import error, util - -HGPATH = 'hg' - -def connect(path=None, encoding=None, configs=None): - ''' starts a cmdserver for the given path (or for a repository found in the - cwd). HGENCODING is set to the given encoding. configs is a list of key, value, - similar to those passed to hg --config. ''' - return hgclient(path, encoding, configs) - -class hgclient(object): - inputfmt = '>I' - outputfmt = '>cI' - outputfmtsize = struct.calcsize(outputfmt) - retfmt = '>i' - - # XXX fix this hack - _stylesdir = os.path.join(os.path.dirname(__file__), 'styles') - revstyle = ['--style', os.path.join(_stylesdir, 'rev.style')] - - revision = collections.namedtuple('revision', 'rev, node, tags, ' - 'branch, author, desc') - - def __init__(self, path, encoding, configs): - args = [HGPATH, 'serve', '--cmdserver', 'pipe'] - if path: - args += ['-R', path] - if configs: - args += ['--config'] + configs - env = dict(os.environ) - if encoding: - env['HGENCODING'] = encoding - - self.server = subprocess.Popen(args, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, env=env) - - self._readhello() - self._config = {} - - def _readhello(self): - """ read the hello message the server sends when started """ - ch, msg = self._readchannel() - assert ch == 'o' - - msg = msg.split('\n') - - self.capabilities = msg[0][len('capabilities: '):] - if not self.capabilities: - raise error.ResponseError("bad hello message: expected 'capabilities: '" - ", got %r" % msg[0]) - - self.capabilities = set(self.capabilities.split()) - - # at the very least the server should be able to run commands - assert 'runcommand' in self.capabilities - - self._encoding = msg[1][len('encoding: '):] - if not self._encoding: - raise error.ResponseError("bad hello message: expected 'encoding: '" - ", got %r" % msg[1]) - - def _readchannel(self): - data = self.server.stdout.read(hgclient.outputfmtsize) - if not data: - raise error.ServerError() - channel, length = struct.unpack(hgclient.outputfmt, data) - if channel in 'IL': - return channel, length - else: - return channel, self.server.stdout.read(length) - - def _parserevs(self, splitted): - ''' splitted is a list of fields according to our rev.style, where each 6 - fields compose one revision. ''' - return [self.revision._make(rev) for rev in util.grouper(6, splitted)] - - def _eatlines(self, s, n): - idx = 0 - for i in xrange(n): - idx = s.find('\n', idx) + 1 - - return s[idx:] - - def runcommand(self, args, inchannels, outchannels): - def writeblock(data): - self.server.stdin.write(struct.pack(self.inputfmt, len(data))) - self.server.stdin.write(data) - self.server.stdin.flush() - - if not self.server: - raise ValueError("server not connected") - - self.server.stdin.write('runcommand\n') - writeblock('\0'.join(args)) - - while True: - channel, data = self._readchannel() - - # input channels - if channel in inchannels: - writeblock(inchannels[channel](data)) - # output channels - elif channel in outchannels: - outchannels[channel](data) - # result channel, command finished - elif channel == 'r': - return struct.unpack(hgclient.retfmt, data)[0] - # a channel that we don't know and can't ignore - elif channel.isupper(): - raise error.ResponseError("unexpected data on required channel '%s'" - % channel) - # optional channel - else: - pass - - def outputruncommand(self, args, inchannels = {}, raiseonerror=True): - ''' run the command specified by args, returning (ret, output, error) ''' - out, err = cStringIO.StringIO(), cStringIO.StringIO() - outchannels = {'o' : out.write, 'e' : err.write} - ret = self.runcommand(args, inchannels, outchannels) - if ret and raiseonerror: - raise error.CommandError(args, ret, out.getvalue(), err.getvalue()) - return ret, out.getvalue(), err.getvalue() - - def close(self): - self.server.stdin.close() - self.server.wait() - ret = self.server.returncode - self.server = None - return ret - - @property - def encoding(self): - """ get the servers encoding """ - if not 'getencoding' in self.capabilities: - raise CapabilityError('getencoding') - - if not self._encoding: - self.server.stdin.write('getencoding\n') - self._encoding = self._readfromchannel('r') - - return self._encoding - - def config(self, refresh=False): - if not self._config or refresh: - self._config.clear() - - ret, out, err = self.outputruncommand(['showconfig']) - if ret: - raise error.CommandError(['showconfig'], ret, out, err) - - for entry in cStringIO.StringIO(out): - k, v = entry.rstrip().split('=', 1) - section, name = k.split('.', 1) - self._config.setdefault(section, {})[name] = v - - return self._config - - def status(self): - ret, out = self.outputruncommand(['status', '-0']) - - d = dict((c, []) for c in 'MARC!?I') - - for entry in out.split('\0'): - if entry: - t, f = entry.split(' ', 1) - d[t].append(f) - - return d - - def log(self, revrange=None): - args = ['log'] + self.revstyle - if revrange: - args.append('-r') - args += revrange - - out = self.outputruncommand(args)[1] - out = out.split('\0')[:-1] - - return self._parserevs(out) - - def incoming(self, revrange=None, path=None): - args = ['incoming'] + self.revstyle - if revrange: - args.append('-r') - args += revrange - - if path: - args += [path] - - ret, out, err = self.outputruncommand(args, raiseonerror=False) - if not ret: - out = self._eatlines(out, 2).split('\0')[:-1] - return self._parserevs(out) - elif ret == 1: - return [] - else: - raise error.CommandError(args, ret, out, err) - - def outgoing(self, revrange=None, path=None): - args = ['outgoing'] + self.revstyle - if revrange: - args.append('-r') - args += revrange - - if path: - args += [path] - - ret, out, err = self.outputruncommand(args, raiseonerror=False) - if not ret: - out = self._eatlines(out, 2).split('\0')[:-1] - return self._parserevs(out) - elif ret == 1: - return [] - else: - raise error.CommandError(args, ret, out, err) - - def commit(self, message, addremove=False): - args = ['commit', '-m', message] - - if addremove: - args += ['-A'] - - self.outputruncommand(args) - - # hope the tip hasn't changed since we committed - return self.tip() - - def import_(self, patch): - if isinstance(patch, str): - fp = open(patch) - else: - assert hasattr(patch, 'read') - assert hasattr(patch, 'readline') - - fp = patch - - try: - inchannels = {'I' : fp.read, 'L' : fp.readline} - self.outputruncommand(['import', '-'], inchannels) - finally: - if fp != patch: - fp.close() - - def root(self): - return self.outputruncommand(['root'])[1].rstrip() - - def clone(self, source='.', dest=None, branch=None, updaterev=None, - revrange=None): - args = ['clone'] - - if branch: - args += ['-b', branch] - if updaterev: - args += ['-u', updaterev] - if revrange: - args.append('-r') - args += revrange - args.append(source) - - if dest: - args.append(dest) - - self.outputruncommand(args) - - def tip(self): - out = self.outputruncommand(['tip'] + self.revstyle)[1] - out = out.split('\0') - - return self._parserevs(out)[0] - - def branch(self, name=None): - if not name: - return self.outputruncommand(['branch'])[1].rstrip() - - def branches(self): - out = self.outputruncommand(['branches'])[1] - branches = {} - for line in out.rstrip().split('\n'): - branch, revnode = line.split() - branches[branch] = self.log(revrange=[revnode.split(':')[0]])[0] - - return branches - - def paths(self, name=None): - if not name: - out = self.outputruncommand(['paths'])[1] - if not out: - return {} - - return dict([s.split(' = ') for s in out.rstrip().split('\n')]) - else: - args = ['paths', name] - ret, out, err = self.outputruncommand(args, raiseonerror=False) - if ret: - raise error.CommandError(args, ret, out, err) - return out.rstrip() - - def cat(self, files, rev=None, output=None): - args = ['cat'] - if rev: - args += ['-r', rev] - if output: - args += ['-o', output] - - args += files - ret, out, err = self.outputruncommand(args) - - if not output: - return out
--- a/tests/test-hglib.py Sat Jul 23 22:54:23 2011 +0300 +++ b/tests/test-hglib.py Sat Jul 23 22:55:36 2011 +0300 @@ -4,9 +4,8 @@ import sys, os, subprocess, cStringIO, shutil, tempfile -# XXX fix this hack sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../') -from hglib import hglib +import hglib class test_hglib(unittest.TestCase): def setUp(self): @@ -14,7 +13,7 @@ os.chdir(self._tmpdir) # until we can run norepo commands in the cmdserver os.system('hg init') - self.client = hglib.connect() + self.client = hglib.open() def tearDown(self): shutil.rmtree(self._tmpdir) @@ -46,7 +45,7 @@ self.client.commit('second') self.client.clone(dest='bar') - bar = hglib.connect('bar') + bar = hglib.open('bar') self.assertEquals(self.client.log(), bar.log()) self.assertEquals(self.client.outgoing(path='bar'), bar.incoming()) @@ -69,14 +68,14 @@ self.assertEquals(rev, branches[rev.branch]) def test_encoding(self): - self.client = hglib.connect(encoding='utf-8') + self.client = hglib.open(encoding='utf-8') self.assertEquals(self.client.encoding, 'utf-8') def test_paths(self): open('.hg/hgrc', 'a').write('[paths]\nfoo = bar\n') # hgrc isn't watched for changes yet, have to reconnect - self.client = hglib.connect() + self.client = hglib.open() paths = self.client.paths() self.assertEquals(len(paths), 1) self.assertEquals(paths['foo'], os.path.abspath('bar'))