From 7b1eb5d73714a8775964711ad3447eb317b98ad9 Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:32:36 -0400 Subject: [PATCH 1/8] DirStack can be a context --- esutil/ostools.py | 55 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/esutil/ostools.py b/esutil/ostools.py index 1c3c672..500479c 100644 --- a/esutil/ostools.py +++ b/esutil/ostools.py @@ -78,6 +78,7 @@ Extract the directory from a file name and create it if it doesn't exist. """ + from __future__ import print_function import os @@ -113,8 +114,19 @@ class DirStack(object): ~/data ~ >>> ds.pop() ~ - + # + # you can use as a context as well. On exiting the + # context, all directories are popped + >>> print(os.getcwd()) + '/home/username' + >>> with DirStack() as ds: + ds.push('/tmp') + print(os.getcwd()) + '/tmp' + >>> print(os.getcwd()) + '/home/username' """ + def __init__(self, verbose=False): self.verbose = verbose self._home = os.path.expanduser('~') @@ -143,7 +155,6 @@ def pop(self): that directory. """ if len(self._dirs) == 0: - stderr.write("Directory stack is empty\n") return dir = self._dirs.pop() @@ -160,7 +171,7 @@ def getstack(self): def print_stack(self): self.print_dir(os.getcwd()) - for i in range(len(self._dirs)-1, -1, -1): + for i in range(len(self._dirs) - 1, -1, -1): d = self._dirs[i] self.print_dir(d) stdout.write('\n') @@ -169,6 +180,13 @@ def print_dir(self, dir): dir = dir.replace(self._home, '~') stdout.write('%s ' % dir) + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + while len(self._dirs) > 0: + self.pop() + def path_join(*paths): """ @@ -258,13 +276,15 @@ def expand_path(filename): expand_filename = expand_path -def exec_process(command, - timeout=None, - poll=1, - stdout_file=subprocess.PIPE, - stderr_file=subprocess.PIPE, - shell=True, - verbose=False): +def exec_process( + command, + timeout=None, + poll=1, + stdout_file=subprocess.PIPE, + stderr_file=subprocess.PIPE, + shell=True, + verbose=False, +): """ Name: exec_process @@ -324,7 +344,7 @@ def exec_process(command, if verbose: print(command[0], ' \\', file=stderr) for c in command[1:]: - print(' '+c+' \\', file=stderr) + print(' ' + c + ' \\', file=stderr) else: cmd = command if verbose: @@ -374,7 +394,7 @@ def _poll_subprocess(pobj, timeout, poll): exit_status = pobj.poll() if exit_status is not None: break - tm = time.time()-tm0 + tm = time.time() - tm0 if tm > timeout: break except KeyboardInterrupt: @@ -385,8 +405,10 @@ def _poll_subprocess(pobj, timeout, poll): # exit status will not be None upon completion. If we passed # the timeout we want to kill the process. if exit_status is None: - stderr.write("Process is taking longer than %s seconds. " - "Ending process\n" % timeout) + stderr.write( + "Process is taking longer than %s seconds. " + "Ending process\n" % timeout + ) os.kill(pobj.pid, signal.SIGTERM) exit_status = 1024 stdout_ret, stderr_ret = None, None @@ -460,6 +482,7 @@ class StagedOutFile(object): ... with open(sf.path, 'w') as fobj: ... fobj.write("some data") """ + def __init__(self, fname, tmpdir=None, must_exist=False): self.must_exist = must_exist self.was_staged_out = False @@ -514,8 +537,7 @@ def stage_out(self): makedirs_fromfile(self.final_path) - print( - "staging out '%s' -> '%s'" % (self.path, self.final_path)) + print("staging out '%s' -> '%s'" % (self.path, self.final_path)) shutil.move(self.path, self.final_path) self.was_staged_out = True @@ -549,6 +571,7 @@ class StagedInFile(object): # read some data """ + def __init__(self, fname, tmpdir=None): self._set_paths(fname, tmpdir=tmpdir) From 96d44d9abbe4b10307c92fd5b6a3581a9be52e98 Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:32:49 -0400 Subject: [PATCH 2/8] add tests of DirStack --- esutil/tests/test_dirstack.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 esutil/tests/test_dirstack.py diff --git a/esutil/tests/test_dirstack.py b/esutil/tests/test_dirstack.py new file mode 100644 index 0000000..97dc8f5 --- /dev/null +++ b/esutil/tests/test_dirstack.py @@ -0,0 +1,27 @@ +def test_dirstack_context(): + import tempfile + import os + from esutil.ostools import DirStack + + with tempfile.TemporaryDirectory() as tmpdir: + orig_dir = os.getcwd() + with DirStack() as ds: + ds.push(tmpdir) + assert os.getcwd() == tmpdir + + assert os.getcwd() == orig_dir + + +def test_dirstack_nocontext(): + import tempfile + import os + from esutil.ostools import DirStack + + with tempfile.TemporaryDirectory() as tmpdir: + orig_dir = os.getcwd() + ds = DirStack() + ds.push(tmpdir) + assert os.getcwd() == tmpdir + ds.pop() + + assert os.getcwd() == orig_dir From 5d4f11e1b1a0a365be5dcec886e623a8c3cc0d3c Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:32:56 -0400 Subject: [PATCH 3/8] update RELEASE_NOTES --- RELEASE_NOTES | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index a768eec..76d830b 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -4,6 +4,7 @@ Enhancements - numpy_util.ahelp can now show dict of arrays + - ostools.DirStack can be used as a context Bug Fixes From c5d9c684e2c2d02eab8540257a14fa9758a1cadf Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:38:47 -0400 Subject: [PATCH 4/8] more py versions --- .github/workflows/test.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7811478..cef097c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - pyver: ["3.8", "3.9", "3.10", "3.11", "3.12"] + pyver: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] compiler: [gcc] include: - os: macos-latest @@ -26,6 +26,12 @@ jobs: - os: macos-latest pyver: "3.12" compiler: clang + - os: macos-latest + pyver: "3.13" + compiler: clang + - os: macos-latest + pyver: "3.14" + compiler: clang runs-on: ${{ matrix.os }} From e612207c6baff60bbf884cfdd5be50883ce8d9fc Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:39:02 -0400 Subject: [PATCH 5/8] simplify module doc --- esutil/ostools.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/esutil/ostools.py b/esutil/ostools.py index 500479c..1cf0e22 100644 --- a/esutil/ostools.py +++ b/esutil/ostools.py @@ -12,42 +12,17 @@ directory stack in Unix shells. See the documentation for the DirStack class for more details. - Example: - >>> ds=esutil.ostools.DirStack(verbose=True) - >>> ds.push('~/data') - ~/data ~ - >>> ds.push('/usr/bin') - /usr/bin ~/data ~ - >>> ds.pop() - ~/data ~ - >>> ds.pop() - ~ - Class Name: StagedOutFile Purpose: A context manager for staging files from temporary directories to a final destination. - Example: - >>> fname = "/home/jill/output.dat" - >>> tmpdir = "/tmp" - >>> with StagedOutFile(fname, tmpdir=tmpdir) as sf: - ... with open(sf.path, 'w') as fobj: - ... fobj.write("some data") - Class Name: StagedInFile Purpose: A class to stage a file in to local disk for reading. - Example: - >>> fname="/home/jill/output.dat" - >>> tmpdir="/tmp" - >>> with StagedInFile(fname,tmpdir=tmpdir) as sf: - ... with open(sf.path) as fobj: - ... # read some data from fobj - Functions: See docs for the individual functions for more info. From cd5011e91975f0463193b265c759191469713580 Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:39:06 -0400 Subject: [PATCH 6/8] try to get tests to work on macos --- esutil/tests/test_dirstack.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/esutil/tests/test_dirstack.py b/esutil/tests/test_dirstack.py index 97dc8f5..ec2d824 100644 --- a/esutil/tests/test_dirstack.py +++ b/esutil/tests/test_dirstack.py @@ -3,7 +3,11 @@ def test_dirstack_context(): import os from esutil.ostools import DirStack - with tempfile.TemporaryDirectory() as tmpdir: + with tempfile.TemporaryDirectory() as tmpdir_orig: + # we do this so the test works on macos + tmpdir = os.path.expandvars(tmpdir_orig) + tmpdir = os.path.expanduser(tmpdir) + orig_dir = os.getcwd() with DirStack() as ds: ds.push(tmpdir) @@ -17,7 +21,12 @@ def test_dirstack_nocontext(): import os from esutil.ostools import DirStack - with tempfile.TemporaryDirectory() as tmpdir: + with tempfile.TemporaryDirectory() as tmpdir_orig: + + # we do this so the test works on macos + tmpdir = os.path.expandvars(tmpdir_orig) + tmpdir = os.path.expanduser(tmpdir) + orig_dir = os.getcwd() ds = DirStack() ds.push(tmpdir) From bd1bc00aeb5943fb1056d43579931fcab8e0318c Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:46:43 -0400 Subject: [PATCH 7/8] try abspath to get tests to work on macos --- esutil/tests/test_dirstack.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/esutil/tests/test_dirstack.py b/esutil/tests/test_dirstack.py index ec2d824..5f9777b 100644 --- a/esutil/tests/test_dirstack.py +++ b/esutil/tests/test_dirstack.py @@ -1,36 +1,34 @@ def test_dirstack_context(): import tempfile import os + from os.path import abspath from esutil.ostools import DirStack - with tempfile.TemporaryDirectory() as tmpdir_orig: - # we do this so the test works on macos - tmpdir = os.path.expandvars(tmpdir_orig) - tmpdir = os.path.expanduser(tmpdir) + with tempfile.TemporaryDirectory() as tmpdir: + # try abspath to get tests to work on macos orig_dir = os.getcwd() + with DirStack() as ds: ds.push(tmpdir) - assert os.getcwd() == tmpdir + assert abspath(os.getcwd()) == abspath(tmpdir) - assert os.getcwd() == orig_dir + assert abspath(os.getcwd()) == abspath(orig_dir) def test_dirstack_nocontext(): import tempfile import os + from os.path import abspath from esutil.ostools import DirStack - with tempfile.TemporaryDirectory() as tmpdir_orig: - - # we do this so the test works on macos - tmpdir = os.path.expandvars(tmpdir_orig) - tmpdir = os.path.expanduser(tmpdir) + with tempfile.TemporaryDirectory() as tmpdir: + # try abspath to get tests to work on macos orig_dir = os.getcwd() ds = DirStack() ds.push(tmpdir) - assert os.getcwd() == tmpdir + assert abspath(os.getcwd()) == abspath(tmpdir) ds.pop() - assert os.getcwd() == orig_dir + assert abspath(os.getcwd()) == abspath(orig_dir) From 8a2bd9cd08c21f4e9bc8a94bf8682bf75af6c2ed Mon Sep 17 00:00:00 2001 From: Erin Sheldon Date: Sun, 28 Jun 2026 13:52:12 -0400 Subject: [PATCH 8/8] try to work around macos oddness --- esutil/tests/test_dirstack.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/esutil/tests/test_dirstack.py b/esutil/tests/test_dirstack.py index 5f9777b..d170cef 100644 --- a/esutil/tests/test_dirstack.py +++ b/esutil/tests/test_dirstack.py @@ -1,34 +1,42 @@ def test_dirstack_context(): import tempfile import os - from os.path import abspath from esutil.ostools import DirStack with tempfile.TemporaryDirectory() as tmpdir: - # try abspath to get tests to work on macos + # on macos CI the actual directory has a different name + # than tmpdir! Do a first run to get that name orig_dir = os.getcwd() + os.chdir(tmpdir) + tmpdir_actual_name = os.getcwd() + os.chdir(orig_dir) with DirStack() as ds: ds.push(tmpdir) - assert abspath(os.getcwd()) == abspath(tmpdir) + assert os.getcwd() == tmpdir_actual_name - assert abspath(os.getcwd()) == abspath(orig_dir) + assert os.getcwd() == orig_dir def test_dirstack_nocontext(): import tempfile import os - from os.path import abspath from esutil.ostools import DirStack with tempfile.TemporaryDirectory() as tmpdir: - # try abspath to get tests to work on macos + # on macos CI the actual directory has a different name + # than tmpdir! Do a first run to get that name + orig_dir = os.getcwd() + os.chdir(tmpdir) + tmpdir_actual_name = os.getcwd() + os.chdir(orig_dir) + ds = DirStack() ds.push(tmpdir) - assert abspath(os.getcwd()) == abspath(tmpdir) + assert os.getcwd() == tmpdir_actual_name ds.pop() - assert abspath(os.getcwd()) == abspath(orig_dir) + assert os.getcwd() == orig_dir