-
Notifications
You must be signed in to change notification settings - Fork 227
/
getstash.py
304 lines (272 loc) · 10.4 KB
/
getstash.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
from __future__ import print_function
import os
import shutil
import sys
import requests
import zipfile
import time
DEFAULT_REPO = "ywangd"
DEFAULT_BRANCH = "master"
TMPDIR = os.environ.get('TMPDIR', os.environ.get('TMP'))
URL_TEMPLATE = 'https://github.com/{}/stash/archive/{}.zip'
TEMP_ZIPFILE = os.path.join(TMPDIR, 'StaSh.zip')
TEMP_PTI = os.path.join(TMPDIR, 'ptinstaller.py')
URL_PTI = 'https://raw.githubusercontent.com/ywangd/pythonista-tools-installer/master/ptinstaller.py'
BASE_DIR = os.path.expanduser('~')
DEFAULT_INSTALL_DIR = os.path.join(BASE_DIR, 'Documents/site-packages/stash')
DEFAULT_PTI_PATH = os.path.join(DEFAULT_INSTALL_DIR, "bin", "ptinstaller.py")
IN_PYTHONISTA = sys.executable.find('Pythonista') >= 0
UNWANTED_FILES = [
'getstash.py',
'run_tests.py',
'testing.py',
'dummyui.py',
'dummyconsole.py',
'bin/pcsm.py',
'bin/bh.py',
'bin/pythonista.py',
'bin/cls.py',
'stash.py',
'lib/librunner.py',
'system/shui.py',
'system/shterminal.py',
'system/dummyui.py',
]
class DownloadError(Exception):
"""
Exception indicating a problem with a download.
"""
pass
def download_stash(repo=DEFAULT_REPO, branch=DEFAULT_BRANCH, outpath=TEMP_ZIPFILE, verbose=False):
"""
Download the StaSh zipfile from github.
:param repo: user owning the repo to download from
:type repo: str
:param branch: branch to download
:type branch: str
:param verbose: if True, print additional information
:type verbose: bool
"""
url = URL_TEMPLATE.format(repo, branch)
if verbose:
print('Downloading {} ...'.format(url))
r = requests.get(url, stream=True)
file_size = r.headers.get('Content-Length')
if file_size is not None:
file_size = int(file_size)
with open(outpath, 'wb') as outs:
block_sz = 8192
for chunk in r.iter_content(block_sz):
outs.write(chunk)
def install_pti(url=URL_PTI, outpath=DEFAULT_PTI_PATH, verbose=False):
"""
Download and install the pythonista tools installer.
:param url: url to download from
:type url: str
:param outpath: path to save to
:type outpath: str
:param verbose: if True, print additional information
:type verbose: bool
"""
if verbose:
print("Downloading {} to {}".format(url, outpath))
r = requests.get(url)
with open(outpath, 'w') as outs:
outs.write(r.text)
def install_from_zip(path=TEMP_ZIPFILE, outpath=DEFAULT_INSTALL_DIR, launcher_path=None, verbose=False):
"""
Install StaSh from its zipfile.
:param path: path to zipfile
:type path: str
:param outpath: path to extract to
:type outpath: str
:param launcher_path: path to install launch_stash.py to
:type launcher_path: str
:param verbose: print additional information
:type verbose: bool
"""
unzip_into(path, outpath, verbose=verbose)
if launcher_path is not None:
# Move launch script to Documents for easy access
shutil.move(os.path.join(outpath, 'launch_stash.py'), launcher_path)
def unzip_into(path, outpath, verbose=False):
"""
Unzip zipfile at path into outpath.
:param path: path to zipfile
:type path: str
:param outpath: path to extract to
:type outpath: str
"""
if not os.path.exists(outpath):
os.makedirs(outpath)
if verbose:
print('Unzipping into %s ...' % outpath)
with zipfile.ZipFile(path) as zipfp:
toplevel_directory = None
namelist = zipfp.namelist()
# find toplevel directory name
for name in namelist:
if os.path.dirname(os.path.normpath(name)) == "":
# is toplevel
toplevel_directory = name
break
for name in namelist:
data = zipfp.read(name)
name = name.split(toplevel_directory, 1)[-1] # strip the top-level directory
if name == '': # skip top-level directory
continue
fname = os.path.join(outpath, name)
if fname.endswith('/'): # A directory
if not os.path.exists(fname):
os.makedirs(fname)
else:
fp = open(fname, 'wb')
try:
fp.write(data)
finally:
fp.close()
def remove_unwanted_files(basepath, reraise=False):
"""
Remove unwanted files.
:param basepath: path os StaSh installation
:type basepath: str
:param reraise: If True, reraise any exception occuring
:type reraise: bool
"""
for fname in UNWANTED_FILES:
try:
os.remove(os.path.join(basepath, fname))
except:
pass
def pythonista_install(install_path, repo=DEFAULT_REPO, branch=DEFAULT_BRANCH, launcher_path=None, zippath=None, verbose=False):
"""
Download and install StaSh and other dependencies for pythonista.
:param install_path: directory to install into
:type install_path: str
:param repo: name of user owning the github repo to download/install from
:type repo: str
:param branch: branch to download/install
:type repo: str
:param launcher_path: path to install launcher to
:type launcher_path: str
:param zippath: if not None, it specifies a path to a StaSh zipfile, otherwise download it from repo:branch
:type zippath: str
:param verbose: if True, print additional information
:type verbose: bool
"""
if zippath is None:
zp = TEMP_ZIPFILE
# download StaSh
try:
download_stash(repo=repo, branch=branch, outpath=zp, verbose=verbose)
except:
raise DownloadError("Unable to download StaSh from {}:{}".format(repo, branch))
else:
if verbose:
print("Using '{}' as source.".format(zippath))
zp = zippath
try:
# install StaSh
install_from_zip(zp, install_path, launcher_path, verbose=verbose)
# install pythonista tools installer
# TODO: should this script realy install it?
pti_path = os.path.join(install_path, "bin", "ptinstaller.py")
install_pti(outpath=pti_path)
finally:
# cleanup
if verbose:
print("Cleaning up...")
if os.path.exists(zp):
os.remove(zp)
remove_unwanted_files(install_path, reraise=False)
def setup_install(repo=DEFAULT_REPO, branch=DEFAULT_BRANCH, install_path=None, as_user=False, zippath=None, dryrun=False, verbose=False):
"""
Download and install StaSh using setup.py
:param repo: name of user owning the github repo to download/install from
:type repo: str
:param branch: branch to download/install
:type repo: str
:param install_path: path to install to (as --prefix)
:type install_path: str
:param as_user: install into user packages
:type as_user: bool
:param zippath: alternative path to zip to install from (default: download from repo:branch)
:param dryrun: if True, pass --dry-run to setup.py
:param verbose: if True, print additional information
:type verbose: bool
"""
if zippath is None:
zp = TEMP_ZIPFILE
# download StaSh
try:
download_stash(repo=repo, branch=branch, outpath=zp, verbose=verbose)
except:
raise DownloadError("Unable to download StaSh from {}:{}".format(repo, branch))
else:
zp = zippath
tp = os.path.join(TMPDIR, "getstash-{}".format(time.time()))
unzip_into(zp, tp, verbose=verbose)
# run setup.py
os.chdir(tp)
argv = ["setup.py", "install"]
if as_user:
argv.append("--user")
if install_path is not None:
argv += ["--prefix", install_path]
if dryrun:
argv.append("--dry-run")
sys.argv = argv
fp = os.path.abspath("setup.py")
ns = {
"__name__": "__main__",
"__file__": fp,
}
with open(fp, "rU") as fin:
content = fin.read()
code = compile(content, fp, "exec", dont_inherit=True)
exec(code, ns, ns)
def main(defs={}):
"""
The main function.
:param defs: namespace which may contain additional parameters
:type defs: dict
"""
# read additional arguments
# These arguments will not be defined when StaSh is normally installed,
# but both selfupdate and tests may specify different values
# i would like to use argparse here, but this must be compatible with older StaSh versions
repo = defs.get("_owner", DEFAULT_REPO) # owner of repo
branch = defs.get("_br", DEFAULT_BRANCH) # target branch
is_update = '_IS_UPDATE' in defs # True if update
install_path = defs.get("_target", None) # target path
launcher_path = defs.get("_launcher_path", None) # target path for launch_stash.py
force_dist = defs.get("_force_dist", None) # force install method
zippath = defs.get("_zippath", None) # alternate path of zipfile to use
dryrun = defs.get("_dryrun", None) # do not do anything if True
asuser = defs.get("_asuser", None) # install as user if True
# find out which install to use
if force_dist is None:
if IN_PYTHONISTA:
dist = "pythonista"
else:
dist = "setup"
else:
dist = force_dist
if dist.lower() == "pythonista":
if install_path is None:
install_path = DEFAULT_INSTALL_DIR
if launcher_path is None:
launcher_path = os.path.join(BASE_DIR, "Documents", "launch_stash.py")
pythonista_install(install_path, repo=repo, branch=branch, launcher_path=launcher_path, zippath=zippath, verbose=True)
elif dist.lower() == "setup":
setup_install(repo, branch, install_path=install_path, zippath=zippath, dryrun=dryrun, as_user=asuser, verbose=True)
else:
raise ValueError("Invalid install type: {}".format(dist))
if not is_update:
# print additional instructions
print('Installation completed.')
print('Please restart Pythonista and run launch_stash.py under the home directory to start StaSh.')
# if __name__ == "__main__":
# print("executing main()")
# main(locals())
main(locals()) # older StaSh versions do not pass __name__="__main__" to getstash.py, so we must run this on toplevel