##// END OF EJS Templates
phabricator: add a contrib script...
Jun Wu -
r33195:36b3febd default
parent child Browse files
Show More
@@ -0,0 +1,98 b''
1 # phabricator.py - simple Phabricator integration
2 #
3 # Copyright 2017 Facebook, Inc.
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7 """simple Phabricator integration
8
9 Config::
10
11 [phabricator]
12 # Phabricator URL
13 url = https://phab.example.com/
14
15 # API token. Get it from https://$HOST/conduit/login/
16 token = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
17 """
18
19 from __future__ import absolute_import
20
21 import json
22
23 from mercurial.i18n import _
24 from mercurial import (
25 error,
26 registrar,
27 url as urlmod,
28 util,
29 )
30
31 cmdtable = {}
32 command = registrar.command(cmdtable)
33
34 def urlencodenested(params):
35 """like urlencode, but works with nested parameters.
36
37 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
38 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
39 urlencode. Note: the encoding is consistent with PHP's http_build_query.
40 """
41 flatparams = util.sortdict()
42 def process(prefix, obj):
43 items = {list: enumerate, dict: lambda x: x.items()}.get(type(obj))
44 if items is None:
45 flatparams[prefix] = obj
46 else:
47 for k, v in items(obj):
48 if prefix:
49 process('%s[%s]' % (prefix, k), v)
50 else:
51 process(k, v)
52 process('', params)
53 return util.urlreq.urlencode(flatparams)
54
55 def readurltoken(repo):
56 """return conduit url, token and make sure they exist
57
58 Currently read from [phabricator] config section. In the future, it might
59 make sense to read from .arcconfig and .arcrc as well.
60 """
61 values = []
62 section = 'phabricator'
63 for name in ['url', 'token']:
64 value = repo.ui.config(section, name)
65 if not value:
66 raise error.Abort(_('config %s.%s is required') % (section, name))
67 values.append(value)
68 return values
69
70 def callconduit(repo, name, params):
71 """call Conduit API, params is a dict. return json.loads result, or None"""
72 host, token = readurltoken(repo)
73 url, authinfo = util.url('/'.join([host, 'api', name])).authinfo()
74 urlopener = urlmod.opener(repo.ui, authinfo)
75 repo.ui.debug('Conduit Call: %s %s\n' % (url, params))
76 params = params.copy()
77 params['api.token'] = token
78 request = util.urlreq.request(url, data=urlencodenested(params))
79 body = urlopener.open(request).read()
80 repo.ui.debug('Conduit Response: %s\n' % body)
81 parsed = json.loads(body)
82 if parsed.get(r'error_code'):
83 msg = (_('Conduit Error (%s): %s')
84 % (parsed[r'error_code'], parsed[r'error_info']))
85 raise error.Abort(msg)
86 return parsed[r'result']
87
88 @command('debugcallconduit', [], _('METHOD'))
89 def debugcallconduit(ui, repo, name):
90 """call Conduit API
91
92 Call parameters are read from stdin as a JSON blob. Result will be written
93 to stdout as a JSON blob.
94 """
95 params = json.loads(ui.fin.read())
96 result = callconduit(repo, name, params)
97 s = json.dumps(result, sort_keys=True, indent=2, separators=(',', ': '))
98 ui.write('%s\n' % s)
General Comments 0
You need to be logged in to leave comments. Login now