Skip to content

Commit 8a2f21a

Browse files
committed
Merge pull request #82 from tony/project-dirs-relativity
Merge pull request for loading projects outside of current working directory. Support for expanding env variables and tildes in ``start_directory``.
2 parents 3887bdc + f994684 commit 8a2f21a

File tree

5 files changed

+160
-12
lines changed

5 files changed

+160
-12
lines changed

CHANGES

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ Here you can find the recent changes to tmuxp.
99

1010
- [config] :meth:`config.expand` now resolves directories in configuration
1111
via :py:func:`os.path.expanduser` and :py:func:`os.path.expandvars`.
12+
- [config] :meth:`config.expandpath` for helping resolve paths.
13+
- [builder] [cli] improved support for loading tmuxp project files from
14+
outside current working directory. e.g.
15+
16+
.. code-block:: bash
17+
18+
$ tmuxp load /path/to/my/project/.tmuxp.yaml
19+
20+
Will behave better with relative directories.
1221

1322
0.1.11
1423
------

doc/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ Import and export
9898

9999
.. automethod:: tmuxp.config.validate_schema
100100

101+
.. automethod:: tmuxp.config.expandpath
102+
101103
.. automethod:: tmuxp.config.expand
102104

103105
.. automethod:: tmuxp.config.inline

tmuxp/config.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ def in_cwd():
102102

103103
return configs
104104

105+
def expandpath(_path):
106+
"""Return expanded path based on user's ``$HOME`` and ``env``.
107+
108+
:py:func:`os.path.expanduser` and :py:func:`os.path.expandvars`
109+
110+
:param _path: path to expand
111+
:type _path: string
112+
:returns: expanded path
113+
:rtype: string
114+
115+
"""
116+
return os.path.expandvars(os.path.expanduser(_path))
105117

106118
def inline(sconf):
107119
""" Return config in inline form, opposite of :meth:`config.expand`.
@@ -139,7 +151,7 @@ def inline(sconf):
139151
return sconf
140152

141153

142-
def expand(sconf, cwd=None):
154+
def expand(sconf, cwd=None, parent=None):
143155
"""Return config with shorthand and inline properties expanded.
144156
145157
This is necessary to keep the code in the :class:`WorkspaceBuilder` clean
@@ -172,11 +184,21 @@ def expand(sconf, cwd=None):
172184
# Any config section, session, window, pane that can contain the
173185
# 'shell_command' value
174186
if 'start_directory' in sconf:
187+
sconf['start_directory'] = expandpath(sconf['start_directory'])
175188
start_path = sconf['start_directory']
176189
if any(start_path.startswith(a) for a in ['.', './']):
190+
# if window has a session, or pane has a window with a
191+
# start_directory of . or ./, make sure the start_directory can be
192+
# relative to the parent.
193+
#
194+
# This is for the case where you may be loading a config from
195+
# outside your shell current directory.
196+
if parent:
197+
cwd = parent['start_directory']
177198
start_path = os.path.normpath(os.path.join(cwd, start_path))
178199
sconf['start_directory'] = start_path
179200

201+
180202
if 'before_script' in sconf:
181203
before_script = sconf['before_script']
182204
if any(before_script.startswith(a) for a in ['.', './']):
@@ -198,7 +220,7 @@ def expand(sconf, cwd=None):
198220
# recurse into window and pane config items
199221
if 'windows' in sconf:
200222
sconf['windows'] = [
201-
expand(window) for window in sconf['windows']
223+
expand(window, parent=sconf) for window in sconf['windows']
202224
]
203225
elif 'panes' in sconf:
204226

@@ -235,7 +257,7 @@ def expand(sconf, cwd=None):
235257
p['shell_command'] = []
236258

237259
pconf.update(p)
238-
sconf['panes'] = [expand(pane) for pane in sconf['panes']]
260+
sconf['panes'] = [expand(pane, parent=sconf) for pane in sconf['panes']]
239261

240262
return sconf
241263

tmuxp/testsuite/config.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ class ExpandTest(TestCase):
212212

213213
after_config = {
214214
'session_name': 'sampleconfig',
215-
'start_directory': '~',
215+
'start_directory': os.path.expanduser('~'),
216216
'windows': [
217217
{
218218
'window_name': 'editor',
@@ -246,19 +246,19 @@ class ExpandTest(TestCase):
246246
]
247247
},
248248
{
249-
'start_directory': os.path.abspath('./'),
249+
'start_directory': os.path.normpath(os.path.join(os.path.join(os.path.expanduser('~'), './'))),
250250
'panes': [
251251
{'shell_command': ['pwd']}
252252
]
253253
},
254254
{
255-
'start_directory': os.path.abspath('./asdf/'),
255+
'start_directory': os.path.normpath(os.path.join(os.path.join(os.path.expanduser('~'), './asdf'))),
256256
'panes': [
257257
{'shell_command': ['pwd']}
258258
]
259259
},
260260
{
261-
'start_directory': os.path.abspath('../'),
261+
'start_directory': os.path.normpath(os.path.join(os.path.expanduser('~'), '../')),
262262
'panes': [
263263
{'shell_command': ['pwd']}
264264
]
@@ -317,7 +317,7 @@ def test_no_window_name(self):
317317

318318
expanded_yaml = """
319319
session_name: sampleconfig
320-
start_directory: '~'
320+
start_directory: {HOME}
321321
windows:
322322
- window_name: focused window
323323
layout: main-horizontal
@@ -348,7 +348,9 @@ def test_no_window_name(self):
348348
focus: true
349349
- shell_command: []
350350
- shell_command: []
351-
"""
351+
""".format(
352+
HOME=os.path.expanduser('~')
353+
)
352354

353355
self.maxDiff = None
354356

@@ -622,7 +624,7 @@ class ShellCommandBeforeTest(TestCase):
622624
'windows': [
623625
{
624626
'window_name': 'editor',
625-
'start_directory': '~',
627+
'start_directory': os.path.expanduser('~'),
626628
'shell_command_before': ['source .env/bin/activate'],
627629
'panes': [
628630
{
@@ -676,7 +678,7 @@ class ShellCommandBeforeTest(TestCase):
676678
'windows': [
677679
{
678680
'window_name': 'editor',
679-
'start_directory': '~',
681+
'start_directory': os.path.expanduser('~'),
680682
'shell_command_before': ['source .env/bin/activate'],
681683
'panes': [
682684
{

tmuxp/testsuite/workspacebuilder.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,8 @@ def tearDown(self):
432432

433433
def test_start_directory(self):
434434

435+
start_directory = os.getcwd()
436+
435437
sconfig = kaptan.Kaptan(handler='yaml')
436438
sconfig = sconfig.import_config(self.yaml_config).get()
437439
sconfig = config.expand(sconfig)
@@ -441,7 +443,117 @@ def test_start_directory(self):
441443
builder.build(session=self.session)
442444

443445
assert(self.session == builder.session)
444-
dirs = ['/usr/bin', '/dev', '/tmp/foo bar', '/usr', os.getcwd()]
446+
dirs = ['/usr/bin', '/dev', '/tmp/foo bar', '/usr', '/usr']
447+
for path, window in zip(dirs, self.session.windows):
448+
for p in window.panes:
449+
for i in range(60):
450+
p.server._update_panes()
451+
if p.get('pane_current_path') == path:
452+
break
453+
time.sleep(.2)
454+
455+
self.assertEqual(p.get('pane_current_path'), path)
456+
457+
458+
class StartDirectoryRelativeTest(TmuxTestCase):
459+
"""Same as above test, but with relative start directory, mimicing
460+
loading it from a location of project file. Like::
461+
462+
$ tmuxp load ~/workspace/myproject/.tmuxp.yaml
463+
464+
instead of::
465+
466+
$ cd ~/workspace/myproject/.tmuxp.yaml
467+
$ tmuxp load .
468+
469+
"""
470+
471+
yaml_config = """
472+
session_name: sampleconfig
473+
start_directory: ./
474+
windows:
475+
- window_name: supposed to be /usr/bin
476+
start_directory: '/usr/bin'
477+
layout: main-horizontal
478+
options:
479+
main-pane-height: 50
480+
panes:
481+
- shell_command:
482+
- echo "hey"
483+
- shell_command:
484+
- echo "moo"
485+
- window_name: support to be /dev
486+
start_directory: '/dev'
487+
layout: main-horizontal
488+
panes:
489+
- shell_command:
490+
- pwd
491+
- shell_command:
492+
- echo "hey"
493+
- shell_command:
494+
- echo "moo"
495+
- window_name: cwd containing a space
496+
start_directory: /tmp/foo bar
497+
layout: main-horizontal
498+
panes:
499+
- shell_command:
500+
- pwd
501+
- shell_command:
502+
- echo "hey"
503+
- shell_command:
504+
- echo "moo"
505+
- window_name: testsa3
506+
layout: main-horizontal
507+
panes:
508+
- shell_command:
509+
- pwd
510+
- shell_command:
511+
- echo "hey"
512+
- shell_command:
513+
- echo "moo3"
514+
- window_name: cwd relative to config file
515+
layout: main-horizontal
516+
start_directory: ./
517+
panes:
518+
- shell_command:
519+
- pwd
520+
- shell_command:
521+
- echo "hey"
522+
- shell_command:
523+
- echo "moo3"
524+
"""
525+
526+
def setUp(self):
527+
super(StartDirectoryRelativeTest, self).setUp()
528+
if not os.path.exists('/tmp/foo bar') and not os.path.exists('/tmp/testRelConfigDir'):
529+
os.mkdir('/tmp/foo bar')
530+
os.mkdir('/tmp/testRelConfigDir')
531+
self._temp_dir_created = True
532+
else:
533+
self._temp_dir_created = False
534+
535+
def tearDown(self):
536+
super(StartDirectoryRelativeTest, self).tearDown()
537+
if self._temp_dir_created:
538+
os.rmdir('/tmp/foo bar')
539+
os.rmdir('/tmp/testRelConfigDir')
540+
541+
def test_start_directory(self):
542+
543+
start_directory = os.getcwd()
544+
545+
sconfig = kaptan.Kaptan(handler='yaml')
546+
sconfig = sconfig.import_config(self.yaml_config).get()
547+
# the second argument of os.getcwd() mimics the behavior
548+
# the CLI loader will do, but it passes in the config file's location.
549+
sconfig = config.expand(sconfig, '/tmp/testRelConfigDir')
550+
sconfig = config.trickle(sconfig)
551+
552+
builder = WorkspaceBuilder(sconf=sconfig)
553+
builder.build(session=self.session)
554+
555+
assert(self.session == builder.session)
556+
dirs = ['/usr/bin', '/dev', '/tmp/foo bar', '/tmp/testRelConfigDir']
445557
for path, window in zip(dirs, self.session.windows):
446558
for p in window.panes:
447559
for i in range(60):
@@ -660,6 +772,7 @@ def suite():
660772
suite.addTest(unittest.makeSuite(FocusAndPaneIndexTest))
661773
suite.addTest(unittest.makeSuite(PaneOrderingTest))
662774
suite.addTest(unittest.makeSuite(StartDirectoryTest))
775+
suite.addTest(unittest.makeSuite(StartDirectoryRelativeTest))
663776
suite.addTest(unittest.makeSuite(ThreePaneTest))
664777
suite.addTest(unittest.makeSuite(TwoPaneTest))
665778
suite.addTest(unittest.makeSuite(WindowAutomaticRename))

0 commit comments

Comments
 (0)