summaryrefslogtreecommitdiffstats
path: root/powerline/lib/shell.py
blob: 2082e82e863348c43d2cb93f3c84a5bed998532a (plain)
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
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)

import sys
import os

from subprocess import Popen, PIPE
from functools import partial

from powerline.lib.encoding import get_preferred_input_encoding, get_preferred_output_encoding


if sys.platform.startswith('win32'):
	# Prevent windows from launching consoles when calling commands
	# http://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
	Popen = partial(Popen, creationflags=0x08000000)


def run_cmd(pl, cmd, stdin=None, strip=True):
	'''Run command and return its stdout, stripped

	If running command fails returns None and logs failure to ``pl`` argument.

	:param PowerlineLogger pl:
		Logger used to log failures.
	:param list cmd:
		Command which will be run.
	:param str stdin:
		String passed to command. May be None.
	:param bool strip:
		True if the result should be stripped.
	'''
	try:
		p = Popen(cmd, shell=False, stdout=PIPE, stdin=PIPE)
	except OSError as e:
		pl.exception('Could not execute command ({0}): {1}', e, cmd)
		return None
	else:
		stdout, err = p.communicate(
			stdin if stdin is None else stdin.encode(get_preferred_output_encoding()))
		stdout = stdout.decode(get_preferred_input_encoding())
	return stdout.strip() if strip else stdout


def asrun(pl, ascript):
	'''Run the given AppleScript and return the standard output and error.'''
	return run_cmd(pl, ['osascript', '-'], ascript)


def readlines(cmd, cwd):
	'''Run command and read its output, line by line

	:param list cmd:
		Command which will be run.
	:param str cwd:
		Working directory of the command which will be run.
	'''
	p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
	encoding = get_preferred_input_encoding()
	p.stderr.close()
	with p.stdout:
		for line in p.stdout:
			yield line[:-1].decode(encoding)


try:
	from shutil import which
except ImportError:
	# shutil.which was added in python-3.3. Here is what was added:
	# Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798
	def which(cmd, mode=os.F_OK | os.X_OK, path=None):
		'''Given a command, mode, and a PATH string, return the path which 
		conforms to the given mode on the PATH, or None if there is no such 
		file.

		``mode`` defaults to os.F_OK | os.X_OK. ``path`` defaults to the result 
		of ``os.environ.get('PATH')``, or can be overridden with a custom search 
		path.
		'''
		# Check that a given file can be accessed with the correct mode.
		# Additionally check that `file` is not a directory, as on Windows
		# directories pass the os.access check.
		def _access_check(fn, mode):
			return (
				os.path.exists(fn)
				and os.access(fn, mode)
				and not os.path.isdir(fn)
			)

		# If we’re given a path with a directory part, look it up directly rather
		# than referring to PATH directories. This includes checking relative to the
		# current directory, e.g. ./script
		if os.path.dirname(cmd):
			if _access_check(cmd, mode):
				return cmd
			return None

		if path is None:
			path = os.environ.get('PATH', os.defpath)
		if not path:
			return None
		path = path.split(os.pathsep)

		if sys.platform == 'win32':
			# The current directory takes precedence on Windows.
			if os.curdir not in path:
				path.insert(0, os.curdir)

			# PATHEXT is necessary to check on Windows.
			pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
			# See if the given file matches any of the expected path extensions.
			# This will allow us to short circuit when given 'python.exe'.
			# If it does match, only test that one, otherwise we have to try
			# others.
			if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
				files = [cmd]
			else:
				files = [cmd + ext for ext in pathext]
		else:
			# On other platforms you don’t have things like PATHEXT to tell you
			# what file suffixes are executable, so just pass on cmd as-is.
			files = [cmd]

		seen = set()
		for dir in path:
			normdir = os.path.normcase(dir)
			if normdir not in seen:
				seen.add(normdir)
				for thefile in files:
					name = os.path.join(dir, thefile)
					if _access_check(name, mode):
						return name
		return None