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
46 LOG = logging.getLogger('swc-windows-installer')
47 LOG.addHandler(logging.StreamHandler())
48 LOG.setLevel(logging.INFO)
51 if sys.version_info >= (3, 0): # Python 3
54 def open3(file, mode='r', newline=None):
57 raise NotImplementedError(newline)
58 f = open(file, mode + 'b')
64 def download(url, sha1):
65 """Download a file and verify it's hash"""
66 LOG.debug('download {}'.format(url))
68 byte_content = r.read()
69 download_sha1 = hashlib.sha1(byte_content).hexdigest()
70 if download_sha1 != sha1:
72 'downloaded {!r} has the wrong SHA-1 hash: {} != {}'.format(
73 url, download_sha1, sha1))
74 LOG.debug('SHA-1 for {} matches the expected {}'.format(url, sha1))
79 """Split a path into a list of components
81 >>> splitall('nano-2.2.6/doc/Makefile.am')
82 ['nano-2.2.6', 'doc', 'Makefile.am']
86 head, tail = os.path.split(path)
98 def transform(tarinfo, strip_components=0):
99 """Transform TarInfo objects for extraction"""
100 path_components = splitall(tarinfo.name)
102 tarinfo.name = os.path.join(*path_components[strip_components:])
104 if len(path_components) <= strip_components:
110 def tar_install(url, sha1, install_directory, compression='*',
112 """Download and install a tar bundle"""
113 if not os.path.isdir(install_directory):
114 tar_bytes = download(url=url, sha1=sha1)
115 tar_io = _BytesIO(tar_bytes)
116 filename = os.path.basename(url)
117 mode = 'r:{}'.format(compression)
118 tar_file = tarfile.open(filename, mode, tar_io)
119 LOG.info('installing {} into {}'.format(url, install_directory))
120 os.makedirs(install_directory)
122 transform(tarinfo=tarinfo, strip_components=strip_components)
123 for tarinfo in tar_file]
125 path=install_directory,
126 members=[m for m in members if m is not None])
128 LOG.info('existing installation at {}'.format(install_directory))
131 def zip_install(url, sha1, install_directory):
132 """Download and install a zipped bundle"""
133 if not os.path.isdir(install_directory):
134 zip_bytes = download(url=url, sha1=sha1)
135 zip_io = _BytesIO(zip_bytes)
136 zip_file = zipfile.ZipFile(zip_io)
137 LOG.info('installing {} into {}'.format(url, install_directory))
138 os.makedirs(install_directory)
139 zip_file.extractall(install_directory)
141 LOG.info('existing installation at {}'.format(install_directory))
144 def install_nano(install_directory):
145 """Download and install the nano text editor"""
147 url='http://www.nano-editor.org/dist/v2.2/NT/nano-2.2.6.zip',
148 sha1='f5348208158157060de0a4df339401f36250fe5b',
149 install_directory=install_directory)
152 def install_nanorc(install_directory):
153 """Download and install nano syntax highlighting"""
155 url='http://www.nano-editor.org/dist/v2.2/nano-2.2.6.tar.gz',
156 sha1='f2a628394f8dda1b9f28c7e7b89ccb9a6dbd302a',
157 install_directory=install_directory,
159 home = os.path.expanduser('~')
160 nanorc = os.path.join(home, 'nano.rc')
161 if not os.path.isfile(nanorc):
162 syntax_dir = os.path.join(install_directory, 'doc', 'syntax')
163 LOG.info('include nanorc from {} in {}'.format(syntax_dir, nanorc))
164 with open3(nanorc, 'w', newline='\n') as f:
165 for filename in os.listdir(syntax_dir):
166 if filename.endswith('.nanorc'):
167 path = os.path.join(syntax_dir, filename)
168 rel_path = os.path.relpath(path, home)
169 include_path = make_posix_path(os.path.join('~', rel_path))
170 f.write('include {}\n'.format(include_path))
173 def install_sqlite(install_directory):
174 """Download and install the SQLite shell"""
176 url='https://sqlite.org/2014/sqlite-shell-win32-x86-3080403.zip',
177 sha1='1a8ab0ca9f4c51afeffeb49bd301e1d7f64741bb',
178 install_directory=install_directory)
181 def create_nosetests_entry_point(python_scripts_directory):
182 """Creates a terminal-based nosetests entry point for msysGit"""
183 contents = '\n'.join([
184 '#!/usr/bin/env/ python',
187 "if __name__ == '__main__':",
188 ' sys.exit(nose.core.main())',
191 if not os.path.isdir(python_scripts_directory):
192 os.makedirs(python_scripts_directory)
193 path = os.path.join(python_scripts_directory, 'nosetests')
194 LOG.info('create nosetests entrypoint {}'.format(path))
195 with open(path, 'w') as f:
199 def get_r_bin_directory():
200 """Locate the R bin directory (if R is installed
202 version_re = re.compile('^R-(\d+)[.](\d+)[.](\d+)$')
205 os.environ.get('ProgramW6432', r'c:\Program Files'),
206 os.environ.get('ProgramFiles', r'c:\Program Files'),
207 os.environ.get('ProgramFiles(x86)', r'c:\Program Files(x86)'),
209 bin_glob = os.path.join(pf, 'R', 'R-[0-9]*.[0-9]*.[0-9]*', 'bin')
210 for path in glob.glob(bin_glob):
211 version_dir = os.path.basename(os.path.dirname(path))
212 version_match = version_re.match(version_dir)
213 if version_match and version_match.groups() not in paths:
214 paths[version_match.groups()] = path
216 LOG.info('no R installation found under {}'.format(pf))
218 LOG.debug('detected R installs:\n* {}'.format('\n* '.join([
219 v for k,v in sorted(paths.items())])))
220 version = sorted(paths.keys())[-1]
221 LOG.info('using R v{} bin directory at {}'.format(
222 '.'.join(version), paths[version]))
223 return paths[version]
226 def update_bash_profile(extra_paths=()):
227 """Create or append to a .bash_profile for Software Carpentry
229 Adds nano to the path, sets the default editor to nano, and adds
230 additional paths for other executables.
234 '# Add paths for Software-Carpentry-installed scripts and executables',
235 'export PATH=\"$PATH:{}\"'.format(':'.join(
236 make_posix_path(path) for path in extra_paths),),
238 '# Make nano the default editor',
239 'export EDITOR=nano',
242 config_path = os.path.join(os.path.expanduser('~'), '.bash_profile')
243 LOG.info('update bash profile at {}'.format(config_path))
244 LOG.debug('extra paths:\n* {}'.format('\n* '.join(extra_paths)))
245 with open(config_path, 'a') as f:
246 f.write('\n'.join(lines))
249 def make_posix_path(windows_path):
250 """Convert a Windows path to a posix path"""
252 (re.compile(r'\\'), '/'),
253 (re.compile('^[Cc]:'), '/c'),
255 windows_path = regex.sub(sub, windows_path)
260 swc_dir = os.path.join(os.path.expanduser('~'), '.swc')
261 bin_dir = os.path.join(swc_dir, 'bin')
262 nano_dir = os.path.join(swc_dir, 'lib', 'nano')
263 nanorc_dir = os.path.join(swc_dir, 'share', 'nanorc')
264 sqlite_dir = os.path.join(swc_dir, 'lib', 'sqlite')
265 create_nosetests_entry_point(python_scripts_directory=bin_dir)
266 install_nano(install_directory=nano_dir)
267 install_nanorc(install_directory=nanorc_dir)
268 install_sqlite(install_directory=sqlite_dir)
269 paths = [nano_dir, sqlite_dir, bin_dir]
270 r_dir = get_r_bin_directory()
273 update_bash_profile(extra_paths=paths)
276 if __name__ == '__main__':
279 parser = argparse.ArgumentParser(
281 formatter_class=argparse.RawDescriptionHelpFormatter)
284 choices=['critical', 'error', 'warning', 'info', 'debug'],
285 help='Verbosity (defaults to {!r})'.format(
286 logging.getLevelName(LOG.level).lower()))
288 '--version', action='version',
289 version='%(prog)s {}'.format(__version__))
291 args = parser.parse_args()
294 level = getattr(logging, args.verbose.upper())
297 LOG.info('Preparing your Software Carpentry awesomeness!')
298 LOG.info('installer version {}'.format(__version__))
300 LOG.info('Installation complete.')