dvc-utils 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dvc_utils/main.py CHANGED
@@ -1,14 +1,11 @@
1
1
  from functools import cache
2
2
  from os import environ as env, getcwd
3
-
4
- from typing import Optional, Tuple
5
-
6
- import shlex
7
3
  from os.path import join, relpath
4
+ import shlex
5
+ from subprocess import Popen, PIPE
6
+ from typing import Optional, Tuple
8
7
 
9
8
  from click import option, argument, group
10
- from subprocess import Popen
11
-
12
9
  import click
13
10
  import yaml
14
11
  from utz import process, singleton, err
@@ -52,7 +49,7 @@ def dvc_cache_dir(log: bool = False) -> str:
52
49
  def dvc_md5(git_ref: str, dvc_path: str, log: bool = False) -> str:
53
50
  dir_path = get_dir_path()
54
51
  dir_path = '' if dir_path == '.' else f'{dir_path}/'
55
- dvc_spec = process.output('git', 'show', f'{git_ref}:{dir_path}{dvc_path}', log=log)
52
+ dvc_spec = process.output('git', 'show', f'{git_ref}:{dir_path}{dvc_path}', log=err if log else None)
56
53
  dvc_obj = yaml.safe_load(dvc_spec)
57
54
  out = singleton(dvc_obj['outs'], dedupe=False)
58
55
  md5 = out['md5']
@@ -72,29 +69,105 @@ def dvc_cache_path(ref: str, dvc_path: Optional[str] = None, log: bool = False)
72
69
  return join(dvc_cache_dir(log=log), 'files', 'md5', dirname, basename)
73
70
 
74
71
 
75
- def diff_cmds(cmd1: str, cmd2: str, verbose: bool = False, **kwargs):
76
- """Run two commands and diff their output.
72
+ def diff_cmds(
73
+ cmds1: list[str],
74
+ cmds2: list[str],
75
+ verbose: bool = False,
76
+ color: bool = False,
77
+ unified: int | None = None,
78
+ ignore_whitespace: bool = False,
79
+ **kwargs,
80
+ ):
81
+ """Run two sequences of piped commands and diff their output.
82
+
83
+ Args:
84
+ cmds1: First sequence of commands to pipe together
85
+ cmds2: Second sequence of commands to pipe together
86
+ verbose: Whether to print commands being executed
87
+ color: Whether to show colored diff output
88
+ unified: Number of unified context lines, or None
89
+ ignore_whitespace: Whether to ignore whitespace changes
90
+ **kwargs: Additional arguments passed to subprocess.Popen
91
+
92
+ Each command sequence will be piped together before being compared.
93
+ For example, if cmds1 = ['cat foo.txt', 'sort'], the function will
94
+ execute 'cat foo.txt | sort' before comparing with cmds2's output.
77
95
 
78
96
  Adapted from https://stackoverflow.com/a/28840955"""
79
97
  with named_pipes(n=2) as pipes:
80
98
  (pipe1, pipe2) = pipes
81
- diff = Popen(['diff'] + pipes)
99
+ diff_cmd = [
100
+ 'diff',
101
+ *(['-w'] if ignore_whitespace else []),
102
+ *(['-U', str(unified)] if unified is not None else []),
103
+ *(['--color=always'] if color else []),
104
+ pipe1,
105
+ pipe2,
106
+ ]
107
+ diff = Popen(diff_cmd)
82
108
  processes = []
83
- for path, cmd in ((pipe1, cmd1), (pipe2, cmd2)):
84
- with open(path, 'wb', 0) as pipe:
85
- if verbose:
86
- err(f"Running: {cmd}")
87
- processes.append(Popen(cmd, stdout=pipe, close_fds=True, **kwargs))
109
+
110
+ for pipe, cmds in ((pipe1, cmds1), (pipe2, cmds2)):
111
+ if verbose:
112
+ err(f"Running pipeline: {' | '.join(cmds)}")
113
+
114
+ # Create the pipeline of processes
115
+ prev_process = None
116
+ for i, cmd in enumerate(cmds):
117
+ is_last = i + 1 == len(cmds)
118
+
119
+ # For the first process, take input from the original source
120
+ stdin = None if prev_process is None else prev_process.stdout
121
+
122
+ # For the last process, output to the named pipe
123
+ if is_last:
124
+ with open(pipe, 'wb', 0) as pipe_fd:
125
+ proc = Popen(
126
+ cmd,
127
+ stdin=stdin,
128
+ stdout=pipe_fd,
129
+ close_fds=True,
130
+ **kwargs
131
+ )
132
+ # For intermediate processes, output to a pipe
133
+ else:
134
+ proc = Popen(
135
+ cmd,
136
+ stdin=stdin,
137
+ stdout=PIPE,
138
+ close_fds=True,
139
+ **kwargs
140
+ )
141
+
142
+ if prev_process is not None:
143
+ prev_process.stdout.close()
144
+
145
+ processes.append(proc)
146
+ prev_process = proc
147
+
88
148
  for p in [diff] + processes:
89
149
  p.wait()
90
150
 
91
151
 
92
152
  @cli.command('diff', short_help='Diff a DVC-tracked file at two commits (or one commit vs. current worktree), optionally passing both through another command first')
153
+ @option('-c', '--color', is_flag=True, help='Colorize the output')
93
154
  @option('-r', '--refspec', default='HEAD', help='<commit 1>..<commit 2> (compare two commits) or <commit> (compare <commit> to the worktree)')
94
155
  @option('-S', '--no-shell', is_flag=True, help="Don't pass `shell=True` to Python `subprocess`es")
156
+ @option('-U', '--unified', type=int, help='Number of lines of context to show (passes through to `diff`)')
95
157
  @option('-v', '--verbose', is_flag=True, help="Log intermediate commands to stderr")
96
- @argument('args', metavar='[cmd...] <path>', nargs=-1)
97
- def dvc_utils_diff(refspec, no_shell, verbose, args):
158
+ @option('-w', '--ignore-whitespace', is_flag=True, help="Ignore whitespace differences (pass `-w` to `diff`)")
159
+ @option('-x', '--exec-cmd', 'exec_cmds', multiple=True, help='Command(s) to execute before diffing; alternate syntax to passing commands as positional arguments')
160
+ @argument('args', metavar='[exec_cmd...] <path>', nargs=-1)
161
+ def dvc_utils_diff(
162
+ color: bool,
163
+ refspec: str | None,
164
+ no_shell: bool,
165
+ unified: int | None,
166
+ verbose: bool,
167
+ ignore_whitespace: bool,
168
+ exec_cmds: Tuple[str, ...],
169
+ args: Tuple[str, ...],
170
+ ):
98
171
  """Diff a file at two commits (or one commit vs. current worktree), optionally passing both through `cmd` first
99
172
 
100
173
  Examples:
@@ -107,14 +180,8 @@ def dvc_utils_diff(refspec, no_shell, verbose, args):
107
180
  raise click.UsageError('Must specify [cmd...] <path>')
108
181
 
109
182
  shell = not no_shell
110
- if len(args) == 2:
111
- cmd, path = args
112
- cmd = shlex.split(cmd)
113
- elif len(args) == 1:
114
- cmd = None
115
- path, = args
116
- else:
117
- raise click.UsageError('Maximum 2 positional args: [cmd] <path>')
183
+ *cmds, path = args
184
+ cmds = list(exec_cmds) + cmds
118
185
 
119
186
  path, dvc_path = dvc_paths(path)
120
187
 
@@ -131,15 +198,33 @@ def dvc_utils_diff(refspec, no_shell, verbose, args):
131
198
  before_path = dvc_cache_path(before, dvc_path, log=log)
132
199
  after_path = path if after is None else dvc_cache_path(after, dvc_path, log=log)
133
200
 
134
- if cmd:
135
- def args(path: str):
136
- arr = cmd + [path]
137
- return shlex.join(arr) if shell else arr
138
-
139
- shell_kwargs = dict(shell=shell) if shell else {}
140
- before_cmd = args(before_path)
141
- after_cmd = args(after_path)
142
- diff_cmds(before_cmd, after_cmd, verbose=verbose, **shell_kwargs)
201
+ if cmds:
202
+ cmd, *sub_cmds = cmds
203
+ if not shell:
204
+ sub_cmds = [ shlex.split(c) for c in sub_cmds ]
205
+ before_cmds = [
206
+ shlex.split(f'{cmd} {before_path}'),
207
+ *sub_cmds,
208
+ ]
209
+ after_cmds = [
210
+ shlex.split(f'{cmd} {after_path}'),
211
+ *sub_cmds,
212
+ ]
213
+ shell_kwargs = {}
214
+ else:
215
+ before_cmds = [ f'{cmd} {before_path}', *sub_cmds ]
216
+ after_cmds = [ f'{cmd} {after_path}', *sub_cmds ]
217
+ shell_kwargs = dict(shell=shell)
218
+
219
+ diff_cmds(
220
+ before_cmds,
221
+ after_cmds,
222
+ verbose=verbose,
223
+ color=color,
224
+ unified=unified,
225
+ ignore_whitespace=ignore_whitespace,
226
+ **shell_kwargs,
227
+ )
143
228
  else:
144
229
  process.run('diff', before_path, after_path, log=log)
145
230
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dvc-utils
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: CLI for diffing DVC files at two commits (or one commit vs. current worktree), optionally passing both through another command first
5
5
  Home-page: https://github.com/runsascoded/dvc-utils
6
6
  Author: Ryan Williams
@@ -29,6 +29,7 @@ pip install dvc-utils
29
29
  ```
30
30
 
31
31
  ## Usage <a id="usage"></a>
32
+ <!-- `bmdf -- dvc-utils --help` -->
32
33
  ```bash
33
34
  dvc-utils --help
34
35
  # Usage: dvc-utils [OPTIONS] COMMAND [ARGS]...
@@ -42,6 +43,7 @@ dvc-utils --help
42
43
  ```
43
44
 
44
45
  ### `dvc-utils diff` <a id="dvc-utils-diff"></a>
46
+ <!-- `bmdf -- dvc-utils diff --help` -->
45
47
  ```bash
46
48
  dvc-utils diff --help
47
49
  # Usage: dvc-utils diff [OPTIONS] [cmd...] <path>
@@ -59,11 +61,15 @@ dvc-utils diff --help
59
61
  # optional) at HEAD (last committed value) vs. the current worktree content.
60
62
  #
61
63
  # Options:
62
- # -r, --refspec TEXT <commit 1>..<commit 2> (compare two commits) or <commit>
63
- # (compare <commit> to the worktree)
64
- # -S, --no-shell Don't pass `shell=True` to Python `subprocess`es
65
- # -v, --verbose Log intermediate commands to stderr
66
- # --help Show this message and exit.
64
+ # -c, --color Colorize the output
65
+ # -r, --refspec TEXT <commit 1>..<commit 2> (compare two commits) or
66
+ # <commit> (compare <commit> to the worktree)
67
+ # -S, --no-shell Don't pass `shell=True` to Python `subprocess`es
68
+ # -U, --unified INTEGER Number of lines of context to show (passes through
69
+ # to `diff`)
70
+ # -v, --verbose Log intermediate commands to stderr
71
+ # -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to `diff`)
72
+ # --help Show this message and exit.
67
73
  ```
68
74
 
69
75
  ## Examples <a id="examples"></a>
@@ -0,0 +1,9 @@
1
+ dvc_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ dvc_utils/main.py,sha256=UhCm28Zu_xwsmfzOKgmAYJrCrfumryid_10F_-Rg-2M,7850
3
+ dvc_utils/named_pipes.py,sha256=VQ2t9BYCazFq_-MABj4t2HS7GHDvSqXXx8fOLz5DsTc,492
4
+ dvc_utils-0.0.5.dist-info/LICENSE,sha256=ZS8AReay7xmQzBAHwxIuTouGXz3SKgUa2_Sz8Ip0EzQ,1070
5
+ dvc_utils-0.0.5.dist-info/METADATA,sha256=y46AoDbejlmv318zUD4aKiY9SprlRh9hdCBcoGZ4H4M,6924
6
+ dvc_utils-0.0.5.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
7
+ dvc_utils-0.0.5.dist-info/entry_points.txt,sha256=W9OuZ6CX8QF9ojbqLtfXFo8Q2hnJ-zlcGY4_7nO8paM,49
8
+ dvc_utils-0.0.5.dist-info/top_level.txt,sha256=jT0-PJa2t_eFRE9rn-52AjdnZ8nQeEHllf2kJmaGh80,10
9
+ dvc_utils-0.0.5.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- dvc_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- dvc_utils/main.py,sha256=0rpJptELszWdFLoCKoYOY6VQtAm2mp-3_-kN075T-TE,4743
3
- dvc_utils/named_pipes.py,sha256=VQ2t9BYCazFq_-MABj4t2HS7GHDvSqXXx8fOLz5DsTc,492
4
- dvc_utils-0.0.3.dist-info/LICENSE,sha256=ZS8AReay7xmQzBAHwxIuTouGXz3SKgUa2_Sz8Ip0EzQ,1070
5
- dvc_utils-0.0.3.dist-info/METADATA,sha256=ExDOJDxXQTUrklQfYm6qTdWNW8Le6xknle4mG2sQFpg,6572
6
- dvc_utils-0.0.3.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
7
- dvc_utils-0.0.3.dist-info/entry_points.txt,sha256=W9OuZ6CX8QF9ojbqLtfXFo8Q2hnJ-zlcGY4_7nO8paM,49
8
- dvc_utils-0.0.3.dist-info/top_level.txt,sha256=jT0-PJa2t_eFRE9rn-52AjdnZ8nQeEHllf2kJmaGh80,10
9
- dvc_utils-0.0.3.dist-info/RECORD,,