Module devtools
[hide private]
[frames] | no frames]

Source Code for Module devtools

  1  import sys 
  2  import os 
  3  import shutil 
  4   
  5  import pytson 
  6   
  7  from itertools import chain 
  8  from zipfile import ZipFile 
  9   
 10  import subprocess 
 11   
 12  import json 
 13  import glob 
14 15 16 -class PluginInstaller(object):
17 """ 18 Class used to install new python plugins and its dependencies. 19 """ 20 21 PLUGIN_SKELETON = """ 22 from ts3plugin import ts3plugin 23 24 class %s(ts3plugin): 25 name = "%s" 26 apiVersion = 21 27 version = "1.0.0" 28 author = "" 29 description = "" 30 requestAutoload = False 31 offersConfigure = False 32 commandKeyword = "" 33 infoTitle = None 34 menuItems = [] 35 hotkeys = [] 36 37 """ 38
39 - def __init__(self, stdout=None):
40 """ 41 @param stdout: A callable used as print function (takes str argument); 42 defaults to None; if None stdout is used instead 43 @type stdout: callable 44 """ 45 self.stdout = stdout 46 47 self.working = False
48 49 @staticmethod
50 - def _escapeString(name):
51 """ 52 Replaces all occurences of non-ascii characters with '_' 53 @param name: the unescaped string 54 @type name: str 55 @return: The escaped string 56 @rtype: str 57 """ 58 return ''.join([c if ord(c) in chain(range(48, 57), range(65, 90), 59 range(97, 122)) 60 else '_' for c in name])
61 62 @staticmethod
63 - def createPlugin(name, withfile=True, content=None):
64 """ 65 Creates the infrastructure for a new plugin. 66 @param name: the name of the plugin 67 @type name: str 68 @param withfile: if True, the file __init__.py is created in the plugin 69 directory, defaults to True 70 @type withfile: bool 71 @param content: content of __ini__.py; defaults to None; if None, an 72 empty plugin skeleton is written to the file (if withfile is True) 73 @type content: str 74 @return: the path to the __init__.py of the new created plugin 75 @rtype: str 76 """ 77 ename = PluginInstaller._escapeString(name) 78 79 p = pytson.getPluginPath("scripts", ename) 80 if os.path.isdir(p): 81 raise Exception("Directory already exist") 82 83 os.mkdir(p) 84 85 fp = os.path.join(p, "__init__.py") 86 if withfile: 87 with open(fp, "w") as f: 88 if content: 89 f.write(content) 90 else: 91 f.write(PluginInstaller.PLUGIN_SKELETON % (ename, ename)) 92 93 return fp
94 95 @staticmethod
96 - def removePlugin(name):
97 """ 98 Uninstall a plugin (delete all data in scripts directory). 99 @param name: the name of the plugin 100 @type name: str 101 """ 102 ename = PluginInstaller._escapeString(name) 103 p = pytson.getPluginPath("scripts", ename) 104 if os.path.isdir(p): 105 shutil.rmtree(p)
106
107 - def _print(self, msg):
108 if self.stdout: 109 self.stdout(msg) 110 else: 111 print(msg)
112 113 @staticmethod
114 - def _extractAddon(path, pkgzip, prefix):
115 if prefix: 116 prefpath = "/".join(prefix) + "/" 117 118 for finfo in pkgzip.infolist(): 119 if not finfo.filename.startswith(prefpath): 120 continue 121 122 relpath = ("/".join([path] + 123 finfo.filename.split("/")[len(prefix):])) 124 if finfo.filename.endswith("/"): 125 if not finfo.filename == prefpath: 126 os.mkdir(relpath) 127 else: 128 with pkgzip.open(finfo) as plugfile: 129 with open(relpath, "wb") as outf: 130 shutil.copyfileobj(plugfile, outf) 131 else: 132 pkgzip.extractall(path)
133
134 - def installPlugin(self, addon, data):
135 """ 136 Installs a new plugin into the scripts directory. 137 @param addon: json dict containing the plugin information 138 @type addon: dict 139 @param data: either the content of a single python file as string or a 140 file-like-object to a zipfile which will be extracted 141 @type data: str or file-like 142 """ 143 if self.working: 144 raise Exception("There is already an installation in progress") 145 else: 146 self.working = True 147 148 fp = self.createPlugin(addon["name"], withfile=False) 149 self._print("Directory created.") 150 151 plat = pytson.platformstr() 152 if ("dependencies" in addon and addon["dependencies"] and 153 plat in addon["dependencies"] and 154 len(addon["dependencies"][plat]) > 0): 155 self._print("Installing dependencies ...") 156 if not self.installPackages(addon["dependencies"][plat]): 157 self._print("Aborting, package installation might be broken" 158 "and needs to be fixed manually") 159 self.working = False 160 return 161 self._print("Done. Installing files ...") 162 163 if type(data) is str: 164 with open(fp, "w") as f: 165 f.write(data) 166 else: 167 with ZipFile(data) as pkgzip: 168 self._extractAddon(os.path.dirname(os.path.abspath(fp)), 169 pkgzip, addon["prefix"] if "prefix" in addon 170 else None) 171 172 self._print("Plugin installed.") 173 self.working = False
174
175 - def installPackages(self, deps):
176 """ 177 Installs packages from pypi.python.org into the include directory. 178 @param deps: A list of package names 179 @type deps: list[str] 180 @return: True on success, False otherwise 181 @rtype: bool 182 """ 183 p = subprocess.Popen([sys.executable, "-m", "pip", "install", 184 "--target", pytson.getPluginPath("include")] + 185 deps, stdout=subprocess.PIPE, 186 stderr=subprocess.PIPE) 187 out, err = p.communicate() 188 189 if err: 190 self._print(err) 191 if out: 192 self._print(out) 193 194 return p.returncode == 0
195
196 197 -def installedPackages():
198 """ 199 Returns a list of installed packages (installed with pip). 200 @returns: a list of dictionaries containing name, version, 201 directory (dir) and dist-info directory (distdir) 202 @rtype: list[dict{str: str}] 203 """ 204 # pip list (or freeze) does not work with the --target option, so 205 # we have to collect the packages manually 206 ret = [] 207 inclpath = pytson.getPluginPath("include") 208 for d in glob.glob(os.path.join(inclpath, "*.dist-info/")): 209 mfile = os.path.join(d, "metadata.json") 210 if os.path.isfile(mfile): 211 with open(mfile, "r") as f: 212 metadata = json.load(f) 213 214 name = metadata["name"] 215 version = metadata["version"] 216 217 tlf = os.path.join(d, "top_level.txt") 218 if os.path.isfile(tlf): 219 with open(tlf, "r") as f: 220 tld = f.read().strip() 221 222 ret.append({"name": name, "version": version, 223 "dir": os.path.join(inclpath, tld), 224 "distdir": d}) 225 226 return ret
227
228 229 -def removePackage(name, version):
230 """ 231 Removes a package (installed with pip). Throws an exception if the 232 package could not be found 233 @param name: the name of the package 234 @type name: str 235 @param version: the version string of the package 236 @type version: str 237 """ 238 inclpath = pytson.getPluginPath("include") 239 240 distdir = os.path.join(inclpath, "%s-%s.dist-info" % (name, version)) 241 if not os.path.isdir(distdir): 242 raise Exception("dist-info directory not found") 243 244 tlf = os.path.join(distdir, "top_level.txt") 245 if not os.path.isfile(tlf): 246 raise Exception("top_level.txt not found") 247 248 with open(tlf, "r") as f: 249 tld = f.read().strip() 250 251 packdir = os.path.join(inclpath, tld) 252 if not os.path.isdir(packdir): 253 raise Exception("package directory not found") 254 255 shutil.rmtree(packdir) 256 shutil.rmtree(distdir)
257