diff --git a/doc/examples.rst b/doc/examples.rst index dd0eb0be4b1..d4f2aedc5c4 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -162,8 +162,8 @@ JSON .. literalinclude:: ../examples/start-directory.json :language: json -Environment variables ---------------------- +Environment variable replacing +------------------------------ tmuxp will replace environment variables wrapped in curly brackets for the following variables: @@ -206,6 +206,23 @@ JSON .. literalinclude:: ../examples/env-variables.json :language: json +Environment variables +--------------------- + +tmuxp will set session environment variables. + +YAML +~~~~ + +.. literalinclude:: ../examples/session-environment.yaml + :language: yaml + +JSON +~~~~ + +.. literalinclude:: ../examples/session-environment.json + :language: json + Focusing -------- diff --git a/examples/session-environment.json b/examples/session-environment.json new file mode 100644 index 00000000000..a2f50b71af8 --- /dev/null +++ b/examples/session-environment.json @@ -0,0 +1,15 @@ +{ + "environment": { + "EDITOR": "/usr/bin/vim", + "HOME": "/tmp/hm", + }, + "windows": [ + { + "panes": [ + null, + ], + "window_name": "Blank pane test" + }, + ], + "session_name": "Environment variables test" +} diff --git a/examples/session-environment.yaml b/examples/session-environment.yaml new file mode 100644 index 00000000000..f5dee52164e --- /dev/null +++ b/examples/session-environment.yaml @@ -0,0 +1,10 @@ +session_name: Environment variables test +environment: + EDITOR: /usr/bin/vim + HOME: /tmp/hm +windows: + # Emptiness will simply open a blank pane, if no shell_command_before. + # All these are equivalent + - window_name: Blank pane test + panes: + - \ No newline at end of file diff --git a/tmuxp/common.py b/tmuxp/common.py new file mode 100644 index 00000000000..0f93314e7d4 --- /dev/null +++ b/tmuxp/common.py @@ -0,0 +1,103 @@ + +class EnvironmentMixin(object): + + """Mixin class for managing session and server level environment + variables in tmux. + + """ + + _add_option = None + + def __init__(self, add_option=None): + self._add_option = add_option + + def set_environment(self, name, value): + """Set environment ``$ tmux set-environment ``. + + :param name: the environment variable name. such as 'PATH'. + :type option: string + :param value: environment value. + :type value: string + + """ + + args = ['set-environment'] + if self._add_option: + args += [self._add_option] + + args += [name, value] + + proc = self.cmd(*args) + + if proc.stderr: + if isinstance(proc.stderr, list) and len(proc.stderr) == int(1): + proc.stderr = proc.stderr[0] + raise ValueError('tmux set-environment stderr: %s' % proc.stderr) + + def unset_environment(self, name): + """Unset environment variable ``$ tmux set-environment -u ``. + + :param name: the environment variable name. such as 'PATH'. + :type option: string + """ + + args = ['set-environment'] + if self._add_option: + args += [self._add_option] + args += ['-u', name] + + proc = self.cmd(*args) + + if proc.stderr: + if isinstance(proc.stderr, list) and len(proc.stderr) == int(1): + proc.stderr = proc.stderr[0] + raise ValueError('tmux set-environment stderr: %s' % proc.stderr) + + def remove_environment(self, name): + """Remove environment variable ``$ tmux set-environment -r ``. + + :param name: the environment variable name. such as 'PATH'. + :type option: string + """ + + args = ['set-environment'] + if self._add_option: + args += [self._add_option] + args += ['-r', name] + + proc = self.cmd(*args) + + if proc.stderr: + if isinstance(proc.stderr, list) and len(proc.stderr) == int(1): + proc.stderr = proc.stderr[0] + raise ValueError('tmux set-environment stderr: %s' % proc.stderr) + + def show_environment(self, name=None): + """Show environment ``$tmux show-environment -t [session] ``. + + Return dict of environment variables for the session or the value of a + specific variable if the name is specified. + + :param name: the environment variable name. such as 'PATH'. + :type option: string + """ + tmux_args = ['show-environment'] + if self._add_option: + tmux_args += [self._add_option] + if name: + tmux_args += [name] + vars = self.cmd(*tmux_args).stdout + vars = [tuple(item.split('=', 1)) for item in vars] + vars_dict = {} + for t in vars: + if len(t) == 2: + vars_dict[t[0]] = t[1] + elif len(t) == 1: + vars_dict[t[0]] = True + else: + raise ValueError('unexpected variable %s', t) + + if name: + return vars_dict.get(name) + + return vars_dict diff --git a/tmuxp/config.py b/tmuxp/config.py index c22f03beffe..1c9a33e1a6d 100644 --- a/tmuxp/config.py +++ b/tmuxp/config.py @@ -189,7 +189,13 @@ def expand(sconf, cwd=None, parent=None): sconf['session_name'] = expandshell(sconf['session_name']) if 'window_name' in sconf: sconf['window_name'] = expandshell(sconf['window_name']) - + if 'environment' in sconf: + for key in sconf['environment']: + val = sconf['environment'][key] + val = expandshell(val) + if any(val.startswith(a) for a in ['.', './']): + val = os.path.normpath(os.path.join(cwd, val)) + sconf['environment'][key] = val # Any config section, session, window, pane that can contain the # 'shell_command' value if 'start_directory' in sconf: diff --git a/tmuxp/server.py b/tmuxp/server.py index bd48792a2f8..8394e170462 100644 --- a/tmuxp/server.py +++ b/tmuxp/server.py @@ -14,11 +14,12 @@ from . import formats, exc from .session import Session from .util import tmux_cmd, TmuxRelationalObject +from .common import EnvironmentMixin logger = logging.getLogger(__name__) -class Server(TmuxRelationalObject): +class Server(TmuxRelationalObject, EnvironmentMixin): """The :term:`tmux(1)` server. @@ -54,6 +55,7 @@ def __init__( colors=None, **kwargs ): + EnvironmentMixin.__init__(self, '-g') self._windows = [] self._panes = [] @@ -383,7 +385,7 @@ def attach_session(self, target_session=None): if proc.stderr: raise exc.TmuxpException(proc.stderr) - + def new_session(self, session_name=None, kill_session=False, diff --git a/tmuxp/session.py b/tmuxp/session.py index b463641275b..c2a0b3ede43 100644 --- a/tmuxp/session.py +++ b/tmuxp/session.py @@ -13,11 +13,12 @@ from . import util, formats, exc from .window import Window +from .common import EnvironmentMixin logger = logging.getLogger(__name__) -class Session(util.TmuxMappingObject, util.TmuxRelationalObject): +class Session(util.TmuxMappingObject, util.TmuxRelationalObject, EnvironmentMixin): """:term:`tmux(1)` session. @@ -28,7 +29,7 @@ class Session(util.TmuxMappingObject, util.TmuxRelationalObject): childIdAttribute = 'window_id' def __init__(self, server=None, **kwargs): - + EnvironmentMixin.__init__(self) self.server = server if not 'session_id' in kwargs: diff --git a/tmuxp/testsuite/server.py b/tmuxp/testsuite/server.py index 054b8cb96f3..e656f7c3a66 100644 --- a/tmuxp/testsuite/server.py +++ b/tmuxp/testsuite/server.py @@ -67,6 +67,29 @@ def test_88_colors(self): self.assertIn('-8', proc.cmd) self.assertNotIn('-2', proc.cmd) +class Environment(TmuxTestCase): + + def test_show_environment(self): + """Server.show_environment() returns dict.""" + + vars = self.server.show_environment() + self.assertIsInstance(vars, dict) + + def test_set_show_environment_single(self): + """Set environment then Server.show_environment(key).""" + + self.server.set_environment('FOO', 'BAR') + self.assertEqual('BAR', self.server.show_environment('FOO')) + + self.server.set_environment('FOO', 'DAR') + self.assertEqual('DAR', self.server.show_environment('FOO')) + + self.assertEqual('DAR', self.server.show_environment()['FOO']) + + def test_show_environment_not_set(self): + """Unset environment variable returns None.""" + self.assertEqual(None, self.server.show_environment('BAR')) + def suite(): suite = unittest.TestSuite() diff --git a/tmuxp/testsuite/session.py b/tmuxp/testsuite/session.py index 093c4f22c8e..fcb6fa29de1 100644 --- a/tmuxp/testsuite/session.py +++ b/tmuxp/testsuite/session.py @@ -140,9 +140,50 @@ def test_set_option_bad(self): self.session.set_option('afewewfew', 43) +class Environment(TmuxTestCase): + + def test_show_environment(self): + """Session.show_environment() returns dict.""" + + vars = self.session.show_environment() + self.assertIsInstance(vars, dict) + + def test_set_show_environment_single(self): + """Set environment then Session.show_environment(key).""" + + self.session.set_environment('FOO', 'BAR') + self.assertEqual('BAR', self.session.show_environment('FOO')) + + self.session.set_environment('FOO', 'DAR') + self.assertEqual('DAR', self.session.show_environment('FOO')) + + self.assertEqual('DAR', self.session.show_environment()['FOO']) + + def test_show_environment_not_set(self): + """Not set environment variable returns None.""" + self.assertEqual(None, self.session.show_environment('BAR')) + + def test_remove_environment(self): + """Remove environment variable.""" + self.assertEqual(None, self.session.show_environment('BAM')) + self.session.set_environment('BAM', 'OK') + self.assertEqual('OK', self.session.show_environment('BAM')) + self.session.remove_environment('BAM') + self.assertEqual(None, self.session.show_environment('BAM')) + + def test_unset_environment(self): + """Unset environment variable.""" + self.assertEqual(None, self.session.show_environment('BAM')) + self.session.set_environment('BAM', 'OK') + self.assertEqual('OK', self.session.show_environment('BAM')) + self.session.unset_environment('BAM') + self.assertEqual(None, self.session.show_environment('BAM')) + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Options)) + suite.addTest(unittest.makeSuite(Environment)) suite.addTest(unittest.makeSuite(SessionNewTest)) suite.addTest(unittest.makeSuite(SessionTest)) return suite diff --git a/tmuxp/testsuite/workspacebuilder.py b/tmuxp/testsuite/workspacebuilder.py index ddc27cf3f33..1cfe7d53b1a 100644 --- a/tmuxp/testsuite/workspacebuilder.py +++ b/tmuxp/testsuite/workspacebuilder.py @@ -186,7 +186,7 @@ def test_focus_pane_index(self): self.assertNotEqual(w.get('window_name'), 'man') pane_path = '/usr' - for i in range(10): + for i in range(20): p = w.attached_pane() p.server._update_panes() if p.get('pane_current_path') == pane_path: @@ -254,6 +254,32 @@ def test_window_options(self): w.select_layout(wconf['layout']) +class EnvironmentVariables(TmuxTestCase): + + yaml_config = """ + session_name: test env vars + start_directory: '~' + environment: + FOO: BAR + PATH: /tmp + windows: + - layout: main-horizontal + panes: + - pane + window_name: editor + """ + + def test_environment_variables(self): + sconfig = kaptan.Kaptan(handler='yaml') + sconfig = sconfig.import_config(self.yaml_config).get() + sconfig = config.expand(sconfig) + + builder = WorkspaceBuilder(sconf=sconfig) + builder.build(self.session) + + self.assertEqual('BAR', self.session.show_environment('FOO')) + self.assertEqual('/tmp', self.session.show_environment('PATH')) + class WindowAutomaticRename(TmuxTestCase): yaml_config = """ @@ -869,4 +895,5 @@ def suite(): suite.addTest(unittest.makeSuite(WindowAutomaticRename)) suite.addTest(unittest.makeSuite(WindowIndexTest)) suite.addTest(unittest.makeSuite(WindowOptions)) + suite.addTest(unittest.makeSuite(EnvironmentVariables)) return suite diff --git a/tmuxp/workspacebuilder.py b/tmuxp/workspacebuilder.py index e0f4281fb9c..1b1f16f5d00 100644 --- a/tmuxp/workspacebuilder.py +++ b/tmuxp/workspacebuilder.py @@ -143,6 +143,9 @@ def build(self, session=None): except Exception as e: self.session.kill_session() raise(e) + if 'environment' in self.sconf: + for option, value in self.sconf['environment'].items(): + self.session.set_environment(option, value) for w, wconf in self.iter_create_windows(session): assert(isinstance(w, Window))