|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the refactoring client interface to rope. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import sys |
|
13 import os |
|
14 |
|
15 sys.path.insert(0, os.path.dirname(__file__)) |
|
16 if sys.version_info[0] >= 3: |
|
17 path = os.path.join(os.path.dirname(__file__), 'rope_py3') |
|
18 else: |
|
19 path = os.path.join(os.path.dirname(__file__), 'rope_py2') |
|
20 sys.path.insert(0, path) |
|
21 |
|
22 try: |
|
23 bytes = unicode |
|
24 import StringIO as io # __IGNORE_EXCEPTION__ |
|
25 except NameError: |
|
26 import io # __IGNORE_WARNING__ |
|
27 |
|
28 import socket |
|
29 import select |
|
30 import traceback |
|
31 import time |
|
32 import json |
|
33 |
|
34 import rope.base.project |
|
35 |
|
36 |
|
37 class RefactoringClient(object): |
|
38 """ |
|
39 Class implementing the refactoring client interface to rope. |
|
40 """ |
|
41 def __init__(self, host, port, projectPath): |
|
42 """ |
|
43 Constructor |
|
44 |
|
45 @param host ip address the background service is listening |
|
46 @type str |
|
47 @param port port of the background service |
|
48 @type int |
|
49 @param projectPath path to the project |
|
50 @type str |
|
51 """ |
|
52 self.__connection = socket.create_connection((host, port)) |
|
53 |
|
54 self.__projectpath = projectPath |
|
55 self.__project = rope.base.project.Project( |
|
56 self.__projectpath)#, fscommands=self.__fsCommands) |
|
57 |
|
58 def __processJson(self, jsonStr): |
|
59 """ |
|
60 Public method to handle a command serialized as a JSON string. |
|
61 |
|
62 @param jsonStr string containing the command received from the IDE |
|
63 @type str |
|
64 """ |
|
65 try: |
|
66 commandDict = json.loads(jsonStr.strip()) |
|
67 except (TypeError, ValueError) as err: |
|
68 self.__sendJson("ClientException", { |
|
69 "ExceptionType": "ProtocolError", |
|
70 "ExceptionValue": str(err), |
|
71 "ProtocolData": jsonStr.strip(), |
|
72 }) |
|
73 return |
|
74 |
|
75 method = commandDict["method"] |
|
76 params = commandDict["params"] |
|
77 if "filename" in params and sys.version_info[0] == 2: |
|
78 params["filename"] = params["filename"].encode( |
|
79 sys.getfilesystemencoding()) |
|
80 |
|
81 if method == "ping": |
|
82 self.__sendJson("pong", {}) |
|
83 |
|
84 def __sendJson(self, command, params): |
|
85 """ |
|
86 Private method to send a single refactoring command to the client. |
|
87 |
|
88 @param command command name to be sent |
|
89 @type str |
|
90 @param params dictionary of named parameters for the command |
|
91 @type dict |
|
92 """ |
|
93 import json |
|
94 |
|
95 commandDict = { |
|
96 "jsonrpc": "2.0", |
|
97 "method": command, |
|
98 "params": params, |
|
99 } |
|
100 cmd = json.dumps(commandDict) + '\n' |
|
101 self.__connection.sendall(cmd.encode('utf8', 'backslashreplace')) |
|
102 |
|
103 def __receiveJson(self): |
|
104 """ |
|
105 Private method to receive a JSON encode command and data from the |
|
106 server. |
|
107 """ |
|
108 line = self.__connection.recv(1024 * 1024, socket.MSG_PEEK) # 1M buffer |
|
109 |
|
110 eol = line.find(b'\n') |
|
111 |
|
112 if eol >= 0: |
|
113 size = eol + 1 |
|
114 |
|
115 # Now we know how big the line is, read it for real. |
|
116 line = self.__connection.recv(size).decode( |
|
117 'utf8', 'backslashreplace') |
|
118 self.__processJson(line) |
|
119 |
|
120 def run(self): |
|
121 """ |
|
122 Public method implementing the main loop of the client. |
|
123 """ |
|
124 try: |
|
125 while True: |
|
126 try: |
|
127 rrdy, wrdy, xrdy = select.select( |
|
128 [self.__connection], [self.__connection], []) |
|
129 except (select.error, KeyboardInterrupt, socket.error): |
|
130 # just carry on |
|
131 continue |
|
132 |
|
133 if self.__connection in rrdy: |
|
134 self.__receiveJson() |
|
135 ## |
|
136 ## if self.__connection in wrdy: |
|
137 ## self.__connection.flush() |
|
138 |
|
139 except Exception: |
|
140 exctype, excval, exctb = sys.exc_info() |
|
141 tbinfofile = io.StringIO() |
|
142 traceback.print_tb(exctb, None, tbinfofile) |
|
143 tbinfofile.seek(0) |
|
144 tbinfo = tbinfofile.read() |
|
145 del exctb |
|
146 self.__sendJson("ClientException", { |
|
147 "ExceptionType": str(exctype), |
|
148 "ExceptionValue": str(excval), |
|
149 "Traceback": tbinfo, |
|
150 }) |
|
151 |
|
152 # Give time to process latest response on server side |
|
153 time.sleep(0.5) |
|
154 self.__connection.shutdown(socket.SHUT_RDWR) |
|
155 self.__connection.close() |
|
156 |
|
157 if __name__ == '__main__': |
|
158 if len(sys.argv) != 4: |
|
159 print('Host, port and project path parameters are missing. Abort.') |
|
160 sys.exit(1) |
|
161 |
|
162 host, port, projectPath = sys.argv[1:] |
|
163 client = RefactoringClient(host, int(port), projectPath) |
|
164 # Start the main loop |
|
165 client.run() |
|
166 |
|
167 # |
|
168 # eflag: noqa = M801 |