3 """Software Carpentry Windows Installer
5 Helps mimic a *nix environment on Windows with as little work as possible.
8 * Installs nano and makes it accessible from msysGit
9 * Installs SQLite and makes it accessible from msysGit
10 * Creates a ~/nano.rc with links to syntax highlighting configs
11 * Provides standard nosetests behavior for msysGit
12 * Adds R's bin directory to the path (if we can find it)
16 1. Install Python, IPython, and Nose. An easy way to do this is with
17 the Anaconda CE Python distribution
18 http://continuum.io/anacondace.html
20 https://github.com/msysgit/msysgit/releases
21 3. Run swc-windows-installer.py.
22 You should be able to simply double click the file in Windows
29 from io import BytesIO as _BytesIO
30 except ImportError: # Python 2
31 from StringIO import StringIO as _BytesIO
38 from urllib.request import urlopen as _urlopen
39 except ImportError: # Python 2
40 from urllib2 import urlopen as _urlopen
44 LOG = logging.getLogger('swc-windows-installer')
45 LOG.addHandler(logging.StreamHandler())
46 LOG.setLevel(logging.INFO)
49 if sys.version_info >= (3, 0): # Python 3
52 def open3(file, mode='r', newline=None):
55 raise NotImplementedError(newline)
56 f = open(file, mode + 'b')
62 def download(url, sha1):
63 """Download a file and verify it's hash"""
64 LOG.debug('download {}'.format(url))
66 byte_content = r.read()
67 download_sha1 = hashlib.sha1(byte_content).hexdigest()
68 if download_sha1 != sha1:
70 'downloaded {!r} has the wrong SHA-1 hash: {} != {}'.format(
71 url, download_sha1, sha1))
72 LOG.debug('SHA-1 for {} matches the expected {}'.format(url, sha1))
77 """Split a path into a list of components
79 >>> splitall('nano-2.2.6/doc/Makefile.am')
80 ['nano-2.2.6', 'doc', 'Makefile.am']
84 head, tail = os.path.split(path)
96 def transform(tarinfo, strip_components=0):
97 """Transform TarInfo objects for extraction"""
98 path_components = splitall(tarinfo.name)
100 tarinfo.name = os.path.join(*path_components[strip_components:])
102 if len(path_components) <= strip_components:
108 def tar_install(url, sha1, install_directory, compression='*',
110 """Download and install a tar bundle"""
111 if not os.path.isdir(install_directory):
112 tar_bytes = download(url=url, sha1=sha1)
113 tar_io = _BytesIO(tar_bytes)
114 filename = os.path.basename(url)
115 mode = 'r:{}'.format(compression)
116 tar_file = tarfile.open(filename, mode, tar_io)
117 LOG.info('installing {} into {}'.format(url, install_directory))
118 os.makedirs(install_directory)
120 transform(tarinfo=tarinfo, strip_components=strip_components)
121 for tarinfo in tar_file]
123 path=install_directory,
124 members=[m for m in members if m is not None])
126 LOG.info('existing installation at {}'.format(install_directory))
129 def zip_install(url, sha1, install_directory):
130 """Download and install a zipped bundle"""
131 if not os.path.isdir(install_directory):
132 zip_bytes = download(url=url, sha1=sha1)
133 zip_io = _BytesIO(zip_bytes)
134 zip_file = zipfile.ZipFile(zip_io)
135 LOG.info('installing {} into {}'.format(url, install_directory))
136 os.makedirs(install_directory)
137 zip_file.extractall(install_directory)
139 LOG.info('existing installation at {}'.format(install_directory))
142 def install_nano(install_directory):
143 """Download and install the nano text editor"""
145 url='http://www.nano-editor.org/dist/v2.2/NT/nano-2.2.6.zip',
146 sha1='f5348208158157060de0a4df339401f36250fe5b',
147 install_directory=install_directory)
150 def install_nanorc(install_directory):
151 """Download and install nano syntax highlighting"""
153 url='http://www.nano-editor.org/dist/v2.2/nano-2.2.6.tar.gz',
154 sha1='f2a628394f8dda1b9f28c7e7b89ccb9a6dbd302a',
155 install_directory=install_directory,
157 home = os.path.expanduser('~')
158 nanorc = os.path.join(home, 'nano.rc')
159 if not os.path.isfile(nanorc):
160 syntax_dir = os.path.join(install_directory, 'doc', 'syntax')
161 LOG.info('include nanorc from {} in {}'.format(syntax_dir, nanorc))
162 with open3(nanorc, 'w', newline='\n') as f:
163 for filename in os.listdir(syntax_dir):
164 if filename.endswith('.nanorc'):
165 path = os.path.join(syntax_dir, filename)
166 rel_path = os.path.relpath(path, home)
167 include_path = make_posix_path(os.path.join('~', rel_path))
168 f.write('include {}\n'.format(include_path))
171 def install_sqlite(install_directory):
172 """Download and install the SQLite shell"""
174 url='https://sqlite.org/2014/sqlite-shell-win32-x86-3080403.zip',
175 sha1='1a8ab0ca9f4c51afeffeb49bd301e1d7f64741bb',
176 install_directory=install_directory)
179 def create_nosetests_entry_point(python_scripts_directory):
180 """Creates a terminal-based nosetests entry point for msysGit"""
181 contents = '\n'.join([
182 '#!/usr/bin/env/ python',
185 "if __name__ == '__main__':",
186 ' sys.exit(nose.core.main())',
189 if not os.path.isdir(python_scripts_directory):
190 os.makedirs(python_scripts_directory)
191 path = os.path.join(python_scripts_directory, 'nosetests')
192 LOG.info('create nosetests entrypoint {}'.format(path))
193 with open(path, 'w') as f:
197 def get_r_bin_directory():
198 """Locate the R bin directory (if R is installed
200 version_re = re.compile('^R-(\d+)[.](\d+)[.](\d+)$')
203 os.environ.get('ProgramW6432', r'c:\Program Files'),
204 os.environ.get('ProgramFiles', r'c:\Program Files'),
205 os.environ.get('ProgramFiles(x86)', r'c:\Program Files(x86)'),
207 bin_glob = os.path.join(pf, 'R', 'R-[0-9]*.[0-9]*.[0-9]*', 'bin')
208 for path in glob.glob(bin_glob):
209 version_dir = os.path.basename(os.path.dirname(path))
210 version_match = version_re.match(version_dir)
211 if version_match and version_match.groups() not in paths:
212 paths[version_match.groups()] = path
214 LOG.info('no R installation found under {}'.format(pf))
216 LOG.debug('detected R installs:\n* {}'.format('\n* '.join([
217 v for k,v in sorted(paths.items())])))
218 version = sorted(paths.keys())[-1]
219 LOG.info('using R v{} bin directory at {}'.format(
220 '.'.join(version), paths[version]))
221 return paths[version]
224 def update_bash_profile(extra_paths=()):
225 """Create or append to a .bash_profile for Software Carpentry
227 Adds nano to the path, sets the default editor to nano, and adds
228 additional paths for other executables.
232 '# Add paths for Software-Carpentry-installed scripts and executables',
233 'export PATH=\"$PATH:{}\"'.format(':'.join(
234 make_posix_path(path) for path in extra_paths),),
236 '# Make nano the default editor',
237 'export EDITOR=nano',
240 config_path = os.path.join(os.path.expanduser('~'), '.bash_profile')
241 LOG.info('update bash profile at {}'.format(config_path))
242 LOG.debug('extra paths:\n* {}'.format('\n* '.join(extra_paths)))
243 with open(config_path, 'a') as f:
244 f.write('\n'.join(lines))
247 def make_posix_path(windows_path):
248 """Convert a Windows path to a posix path"""
250 (re.compile(r'\\'), '/'),
251 (re.compile('^[Cc]:'), '/c'),
253 windows_path = regex.sub(sub, windows_path)
258 swc_dir = os.path.join(os.path.expanduser('~'), '.swc')
259 bin_dir = os.path.join(swc_dir, 'bin')
260 nano_dir = os.path.join(swc_dir, 'lib', 'nano')
261 nanorc_dir = os.path.join(swc_dir, 'share', 'nanorc')
262 sqlite_dir = os.path.join(swc_dir, 'lib', 'sqlite')
263 create_nosetests_entry_point(python_scripts_directory=bin_dir)
264 install_nano(install_directory=nano_dir)
265 install_nanorc(install_directory=nanorc_dir)
266 install_sqlite(install_directory=sqlite_dir)
267 paths = [nano_dir, sqlite_dir, bin_dir]
268 r_dir = get_r_bin_directory()
271 update_bash_profile(extra_paths=paths)
274 if __name__ == '__main__':
277 parser = argparse.ArgumentParser(
279 formatter_class=argparse.RawDescriptionHelpFormatter)
282 choices=['critical', 'error', 'warning', 'info', 'debug'],
283 help='Verbosity (defaults to {!r})'.format(
284 logging.getLevelName(LOG.level).lower()))
286 args = parser.parse_args()
289 level = getattr(logging, args.verbose.upper())
292 LOG.info('Preparing your Software Carpentry awesomeness!')
294 LOG.info('Installation complete.')