dffs 0.0.5__tar.gz
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.
- dffs-0.0.5/LICENSE +21 -0
- dffs-0.0.5/PKG-INFO +278 -0
- dffs-0.0.5/README.md +267 -0
- dffs-0.0.5/dffs/__init__.py +1 -0
- dffs-0.0.5/dffs/cli.py +9 -0
- dffs-0.0.5/dffs/comm_x/__init__.py +56 -0
- dffs-0.0.5/dffs/diff_x/__init__.py +57 -0
- dffs-0.0.5/dffs/git_diff_x/__init__.py +110 -0
- dffs-0.0.5/dffs/utils.py +73 -0
- dffs-0.0.5/dffs.egg-info/PKG-INFO +278 -0
- dffs-0.0.5/dffs.egg-info/SOURCES.txt +15 -0
- dffs-0.0.5/dffs.egg-info/dependency_links.txt +1 -0
- dffs-0.0.5/dffs.egg-info/entry_points.txt +4 -0
- dffs-0.0.5/dffs.egg-info/requires.txt +2 -0
- dffs-0.0.5/dffs.egg-info/top_level.txt +1 -0
- dffs-0.0.5/setup.cfg +4 -0
- dffs-0.0.5/setup.py +23 -0
dffs-0.0.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Runs as Coded
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dffs-0.0.5/PKG-INFO
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dffs
|
|
3
|
+
Version: 0.0.5
|
|
4
|
+
Summary: Pipe and diff files: execute shell pipelines against multiple inputs, diff/compare/join results.
|
|
5
|
+
Home-page: https://github.com/runsascoded/dffs
|
|
6
|
+
Author: Ryan Williams
|
|
7
|
+
Author-email: ryan@runsascoded.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
# dffs
|
|
13
|
+
Pipe and diff files: execute shell pipelines against multiple inputs, diff/compare/join results.
|
|
14
|
+
|
|
15
|
+
[][PyPI]
|
|
16
|
+
<!-- toc -->
|
|
17
|
+
- [Install](#install)
|
|
18
|
+
- [CLIs](#CLIs)
|
|
19
|
+
- [`diff-x`](#diff-x)
|
|
20
|
+
- [Usage](#diff-x-usage)
|
|
21
|
+
- [Examples](#diff-x-examples)
|
|
22
|
+
- [`comm-x`](#comm-x)
|
|
23
|
+
- [Usage](#comm-x-usage)
|
|
24
|
+
- [Examples](#comm-x-examples)
|
|
25
|
+
- [`git-diff-x`](#git-diff-x)
|
|
26
|
+
- [Usage](#git-diff-x-usage)
|
|
27
|
+
- [Examples](#git-diff-x-examples)
|
|
28
|
+
<!-- /toc -->
|
|
29
|
+
|
|
30
|
+
## Install <a id="install"></a>
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install dffs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## CLIs <a id="CLIs"></a>
|
|
37
|
+
|
|
38
|
+
### `diff-x` <a id="diff-x"></a>
|
|
39
|
+
|
|
40
|
+
#### Usage <a id="diff-x-usage"></a>
|
|
41
|
+
<!-- `bmdf diff-x` -->
|
|
42
|
+
```bash
|
|
43
|
+
diff-x
|
|
44
|
+
# Usage: diff-x [OPTIONS] [exec_cmd...] <path1> <path2>
|
|
45
|
+
#
|
|
46
|
+
# Diff two files after running them through a pipeline of other commands.
|
|
47
|
+
#
|
|
48
|
+
# Options:
|
|
49
|
+
# -c, --color Colorize the output
|
|
50
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
51
|
+
# to $SHELL
|
|
52
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
53
|
+
# `subprocess`es
|
|
54
|
+
# -U, --unified INTEGER Number of lines of context to show (passes
|
|
55
|
+
# through to `diff`)
|
|
56
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
57
|
+
# -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to
|
|
58
|
+
# `diff`)
|
|
59
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
60
|
+
# alternate syntax to passing commands as
|
|
61
|
+
# positional arguments
|
|
62
|
+
# --help Show this message and exit.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Examples <a id="diff-x-examples"></a>
|
|
66
|
+
|
|
67
|
+
Given two similar JSON objects, where one is compact and the other is pretty-printed:
|
|
68
|
+
```bash
|
|
69
|
+
echo '{"a":1,"b":2}' > 1.json
|
|
70
|
+
echo '{"a":1,"b":3}' | jq > 2.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`diff {1,2}.json` outputs the entirety of both objects:
|
|
74
|
+
```diff
|
|
75
|
+
1c1,4
|
|
76
|
+
< {"a":1,"b":2}
|
|
77
|
+
---
|
|
78
|
+
> {
|
|
79
|
+
> "a": 1,
|
|
80
|
+
> "b": 3
|
|
81
|
+
> }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`diff-x 'jq .' {1,2}.json` pretty-prints each side before `diff`ing:
|
|
85
|
+
```diff
|
|
86
|
+
3c3
|
|
87
|
+
< "b": 2
|
|
88
|
+
---
|
|
89
|
+
> "b": 3
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `comm-x` <a id="comm-x"></a>
|
|
93
|
+
`comm` essentially performs set intersection/difference; `comm-x` allows you to run a pipeline of commands on each input, before comparing them.
|
|
94
|
+
|
|
95
|
+
#### Usage <a id="comm-x-usage"></a>
|
|
96
|
+
<!-- `bmdf comm-x` -->
|
|
97
|
+
```bash
|
|
98
|
+
comm-x
|
|
99
|
+
# Usage: comm-x [OPTIONS] [exec_cmd...] <path1> <path2>
|
|
100
|
+
#
|
|
101
|
+
# Select or reject lines common to two input streams, after running each
|
|
102
|
+
# through a pipeline of other commands.
|
|
103
|
+
#
|
|
104
|
+
# Options:
|
|
105
|
+
# -1, --exclude-1 Exclude lines only found in the first pipeline
|
|
106
|
+
# -2, --exclude-2 Exclude lines only found in the second pipeline
|
|
107
|
+
# -3, --exclude-3 Exclude lines found in both pipelines
|
|
108
|
+
# -i, --case-insensitive Case insensitive comparison
|
|
109
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
110
|
+
# to $SHELL
|
|
111
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
112
|
+
# `subprocess`es
|
|
113
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
114
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
115
|
+
# alternate syntax to passing commands as
|
|
116
|
+
# positional arguments
|
|
117
|
+
# --help Show this message and exit.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Examples <a id="comm-x-examples"></a>
|
|
121
|
+
Given two similar lists of numbers, but in different orders:
|
|
122
|
+
```bash
|
|
123
|
+
seq 10 > 1.txt
|
|
124
|
+
seq 10 -2 0 > 2.txt
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`comm` outputs gibberish, because the files aren't in sorted order:
|
|
128
|
+
<!-- `bmdf comm 1.txt 2.txt` -->
|
|
129
|
+
```bash
|
|
130
|
+
comm 1.txt 2.txt
|
|
131
|
+
# 1
|
|
132
|
+
# 10
|
|
133
|
+
# 2
|
|
134
|
+
# 3
|
|
135
|
+
# 4
|
|
136
|
+
# 5
|
|
137
|
+
# 6
|
|
138
|
+
# 7
|
|
139
|
+
# 8
|
|
140
|
+
# comm: file 2 is not in sorted order
|
|
141
|
+
# 6
|
|
142
|
+
# 4
|
|
143
|
+
# 2
|
|
144
|
+
# 0
|
|
145
|
+
# 9
|
|
146
|
+
# comm: file 1 is not in sorted order
|
|
147
|
+
# 10
|
|
148
|
+
# comm: input is not in sorted order
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`comm-x sort` sorts each file first:
|
|
152
|
+
<!-- `bmdf comm-x sort 1.txt 2.txt` -->
|
|
153
|
+
```bash
|
|
154
|
+
comm-x sort 1.txt 2.txt
|
|
155
|
+
# 0
|
|
156
|
+
# 1
|
|
157
|
+
# 10
|
|
158
|
+
# 2
|
|
159
|
+
# 3
|
|
160
|
+
# 4
|
|
161
|
+
# 5
|
|
162
|
+
# 6
|
|
163
|
+
# 7
|
|
164
|
+
# 8
|
|
165
|
+
# 9
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `git-diff-x` <a id="git-diff-x"></a>
|
|
169
|
+
|
|
170
|
+
#### Usage <a id="git-diff-x-usage"></a>
|
|
171
|
+
<!-- `bmdf -- git-diff-x --help` -->
|
|
172
|
+
```bash
|
|
173
|
+
git-diff-x --help
|
|
174
|
+
# Usage: git-diff-x [OPTIONS] [exec_cmd...] [<path> | - [paths...]]
|
|
175
|
+
#
|
|
176
|
+
# Diff files at two commits, or one commit and the current worktree, after
|
|
177
|
+
# applying an optional command pipeline.
|
|
178
|
+
#
|
|
179
|
+
# Examples:
|
|
180
|
+
#
|
|
181
|
+
# # Compare the number of lines (`wc -l`) in file `foo` at the previous vs.
|
|
182
|
+
# current commit (`-r HEAD^..HEAD`):
|
|
183
|
+
#
|
|
184
|
+
# git diff-x -r HEAD^..HEAD wc -l foo
|
|
185
|
+
#
|
|
186
|
+
# # Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed
|
|
187
|
+
# value) vs. the current worktree content:
|
|
188
|
+
#
|
|
189
|
+
# git diff-x -c md5sum foo
|
|
190
|
+
#
|
|
191
|
+
# # Use `-` to separate pipeline commands from paths (when more than one path
|
|
192
|
+
# is to be diffed), e.g. this compares the largest 10 numbers in `file{1,2}`
|
|
193
|
+
# (HEAD vs. worktree):
|
|
194
|
+
#
|
|
195
|
+
# git diff-x 'sort -rn' head - file1 file2
|
|
196
|
+
#
|
|
197
|
+
# Options:
|
|
198
|
+
# -c, --color Colorize the output
|
|
199
|
+
# -r, --refspec TEXT <commit 1>..<commit 2> (compare two commits) or
|
|
200
|
+
# <commit> (compare <commit> to the worktree)
|
|
201
|
+
# -R, --ref TEXT Diff a specific commit; alias for `-r
|
|
202
|
+
# <ref>^..<ref>`
|
|
203
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
204
|
+
# to $SHELL
|
|
205
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
206
|
+
# `subprocess`es
|
|
207
|
+
# -U, --unified INTEGER Number of lines of context to show (passes
|
|
208
|
+
# through to `diff`)
|
|
209
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
210
|
+
# -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to
|
|
211
|
+
# `diff`)
|
|
212
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
213
|
+
# alternate syntax to passing commands as
|
|
214
|
+
# positional arguments
|
|
215
|
+
# --help Show this message and exit.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Examples <a id="git-diff-x-examples"></a>
|
|
219
|
+
Compare line-count (`wc -l`) of this README, before and after commit `8b7a761`:
|
|
220
|
+
<!-- `bmdf -- git-diff-x -R 8b7a761 'wc -l' README.md` -->
|
|
221
|
+
```bash
|
|
222
|
+
git-diff-x -R 8b7a761 'wc -l' README.md
|
|
223
|
+
# 1c1
|
|
224
|
+
# < 16
|
|
225
|
+
# ---
|
|
226
|
+
# > 206
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Examples from `--help` above:
|
|
230
|
+
```bash
|
|
231
|
+
# Compare the number of lines (`wc -l`) in file `foo` at the previous vs. current commit
|
|
232
|
+
# (`-R HEAD` is equivalent to `-r HEAD^..HEAD`).
|
|
233
|
+
git diff-x -R HEAD wc -l foo
|
|
234
|
+
|
|
235
|
+
# Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed value) vs. the current
|
|
236
|
+
# worktree content.
|
|
237
|
+
git diff-x -c md5sum foo
|
|
238
|
+
|
|
239
|
+
# Use `-` to separate pipeline commands from paths (when more than one path is to be diffed),
|
|
240
|
+
# e.g. this compares the largest 10 numbers in `file{1,2}` (HEAD vs. worktree):
|
|
241
|
+
git diff-x 'sort -rn' head - file1 file2
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
I use `git-diff-x` via several Git and Bash aliases:
|
|
245
|
+
|
|
246
|
+
[`.gitconfig`][diff-x git configs]:
|
|
247
|
+
```.gitconfig
|
|
248
|
+
[alias]
|
|
249
|
+
; pip install dffs
|
|
250
|
+
dx = diff-x
|
|
251
|
+
dxc = diff-x -c
|
|
252
|
+
dxcr = diff-x -cR
|
|
253
|
+
dxcrr = diff-x -cr
|
|
254
|
+
dxr = diff-x -R
|
|
255
|
+
dxrr = diff-x -r
|
|
256
|
+
dxw = diff-x -w
|
|
257
|
+
dxwr = diff-x -wR
|
|
258
|
+
dxwrr = diff-x -wr
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
[`.git-rc`][diff-x aliases]:
|
|
262
|
+
```bash
|
|
263
|
+
alias gdx="g dx"
|
|
264
|
+
alias gdxc="g dxc"
|
|
265
|
+
alias gdxcr="g dxcr"
|
|
266
|
+
alias gdxcrr="g dxcrr"
|
|
267
|
+
alias gdxr="g dxr"
|
|
268
|
+
alias gdxrr="g dxrr"
|
|
269
|
+
alias gdxw="g dxw"
|
|
270
|
+
alias gdxwr="g dxwr"
|
|
271
|
+
alias gdxwrr="g dxwrr"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
[`jq`]: https://stedolan.github.io/jq/
|
|
275
|
+
[PyPI]: https://pypi.org/project/dffs/
|
|
276
|
+
|
|
277
|
+
[diff-x git configs]: https://github.com/ryan-williams/git-helpers/blob/5f27c2e4e88e3e14ede21483c998bdbe2cfccc6f/diff/.gitconfig#L64-L73
|
|
278
|
+
[diff-x aliases]: https://github.com/ryan-williams/git-helpers/blob/5f27c2e4e88e3e14ede21483c998bdbe2cfccc6f/diff/.git-rc#L59-L67
|
dffs-0.0.5/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# dffs
|
|
2
|
+
Pipe and diff files: execute shell pipelines against multiple inputs, diff/compare/join results.
|
|
3
|
+
|
|
4
|
+
[][PyPI]
|
|
5
|
+
<!-- toc -->
|
|
6
|
+
- [Install](#install)
|
|
7
|
+
- [CLIs](#CLIs)
|
|
8
|
+
- [`diff-x`](#diff-x)
|
|
9
|
+
- [Usage](#diff-x-usage)
|
|
10
|
+
- [Examples](#diff-x-examples)
|
|
11
|
+
- [`comm-x`](#comm-x)
|
|
12
|
+
- [Usage](#comm-x-usage)
|
|
13
|
+
- [Examples](#comm-x-examples)
|
|
14
|
+
- [`git-diff-x`](#git-diff-x)
|
|
15
|
+
- [Usage](#git-diff-x-usage)
|
|
16
|
+
- [Examples](#git-diff-x-examples)
|
|
17
|
+
<!-- /toc -->
|
|
18
|
+
|
|
19
|
+
## Install <a id="install"></a>
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install dffs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## CLIs <a id="CLIs"></a>
|
|
26
|
+
|
|
27
|
+
### `diff-x` <a id="diff-x"></a>
|
|
28
|
+
|
|
29
|
+
#### Usage <a id="diff-x-usage"></a>
|
|
30
|
+
<!-- `bmdf diff-x` -->
|
|
31
|
+
```bash
|
|
32
|
+
diff-x
|
|
33
|
+
# Usage: diff-x [OPTIONS] [exec_cmd...] <path1> <path2>
|
|
34
|
+
#
|
|
35
|
+
# Diff two files after running them through a pipeline of other commands.
|
|
36
|
+
#
|
|
37
|
+
# Options:
|
|
38
|
+
# -c, --color Colorize the output
|
|
39
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
40
|
+
# to $SHELL
|
|
41
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
42
|
+
# `subprocess`es
|
|
43
|
+
# -U, --unified INTEGER Number of lines of context to show (passes
|
|
44
|
+
# through to `diff`)
|
|
45
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
46
|
+
# -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to
|
|
47
|
+
# `diff`)
|
|
48
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
49
|
+
# alternate syntax to passing commands as
|
|
50
|
+
# positional arguments
|
|
51
|
+
# --help Show this message and exit.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Examples <a id="diff-x-examples"></a>
|
|
55
|
+
|
|
56
|
+
Given two similar JSON objects, where one is compact and the other is pretty-printed:
|
|
57
|
+
```bash
|
|
58
|
+
echo '{"a":1,"b":2}' > 1.json
|
|
59
|
+
echo '{"a":1,"b":3}' | jq > 2.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`diff {1,2}.json` outputs the entirety of both objects:
|
|
63
|
+
```diff
|
|
64
|
+
1c1,4
|
|
65
|
+
< {"a":1,"b":2}
|
|
66
|
+
---
|
|
67
|
+
> {
|
|
68
|
+
> "a": 1,
|
|
69
|
+
> "b": 3
|
|
70
|
+
> }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`diff-x 'jq .' {1,2}.json` pretty-prints each side before `diff`ing:
|
|
74
|
+
```diff
|
|
75
|
+
3c3
|
|
76
|
+
< "b": 2
|
|
77
|
+
---
|
|
78
|
+
> "b": 3
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `comm-x` <a id="comm-x"></a>
|
|
82
|
+
`comm` essentially performs set intersection/difference; `comm-x` allows you to run a pipeline of commands on each input, before comparing them.
|
|
83
|
+
|
|
84
|
+
#### Usage <a id="comm-x-usage"></a>
|
|
85
|
+
<!-- `bmdf comm-x` -->
|
|
86
|
+
```bash
|
|
87
|
+
comm-x
|
|
88
|
+
# Usage: comm-x [OPTIONS] [exec_cmd...] <path1> <path2>
|
|
89
|
+
#
|
|
90
|
+
# Select or reject lines common to two input streams, after running each
|
|
91
|
+
# through a pipeline of other commands.
|
|
92
|
+
#
|
|
93
|
+
# Options:
|
|
94
|
+
# -1, --exclude-1 Exclude lines only found in the first pipeline
|
|
95
|
+
# -2, --exclude-2 Exclude lines only found in the second pipeline
|
|
96
|
+
# -3, --exclude-3 Exclude lines found in both pipelines
|
|
97
|
+
# -i, --case-insensitive Case insensitive comparison
|
|
98
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
99
|
+
# to $SHELL
|
|
100
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
101
|
+
# `subprocess`es
|
|
102
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
103
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
104
|
+
# alternate syntax to passing commands as
|
|
105
|
+
# positional arguments
|
|
106
|
+
# --help Show this message and exit.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Examples <a id="comm-x-examples"></a>
|
|
110
|
+
Given two similar lists of numbers, but in different orders:
|
|
111
|
+
```bash
|
|
112
|
+
seq 10 > 1.txt
|
|
113
|
+
seq 10 -2 0 > 2.txt
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`comm` outputs gibberish, because the files aren't in sorted order:
|
|
117
|
+
<!-- `bmdf comm 1.txt 2.txt` -->
|
|
118
|
+
```bash
|
|
119
|
+
comm 1.txt 2.txt
|
|
120
|
+
# 1
|
|
121
|
+
# 10
|
|
122
|
+
# 2
|
|
123
|
+
# 3
|
|
124
|
+
# 4
|
|
125
|
+
# 5
|
|
126
|
+
# 6
|
|
127
|
+
# 7
|
|
128
|
+
# 8
|
|
129
|
+
# comm: file 2 is not in sorted order
|
|
130
|
+
# 6
|
|
131
|
+
# 4
|
|
132
|
+
# 2
|
|
133
|
+
# 0
|
|
134
|
+
# 9
|
|
135
|
+
# comm: file 1 is not in sorted order
|
|
136
|
+
# 10
|
|
137
|
+
# comm: input is not in sorted order
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`comm-x sort` sorts each file first:
|
|
141
|
+
<!-- `bmdf comm-x sort 1.txt 2.txt` -->
|
|
142
|
+
```bash
|
|
143
|
+
comm-x sort 1.txt 2.txt
|
|
144
|
+
# 0
|
|
145
|
+
# 1
|
|
146
|
+
# 10
|
|
147
|
+
# 2
|
|
148
|
+
# 3
|
|
149
|
+
# 4
|
|
150
|
+
# 5
|
|
151
|
+
# 6
|
|
152
|
+
# 7
|
|
153
|
+
# 8
|
|
154
|
+
# 9
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `git-diff-x` <a id="git-diff-x"></a>
|
|
158
|
+
|
|
159
|
+
#### Usage <a id="git-diff-x-usage"></a>
|
|
160
|
+
<!-- `bmdf -- git-diff-x --help` -->
|
|
161
|
+
```bash
|
|
162
|
+
git-diff-x --help
|
|
163
|
+
# Usage: git-diff-x [OPTIONS] [exec_cmd...] [<path> | - [paths...]]
|
|
164
|
+
#
|
|
165
|
+
# Diff files at two commits, or one commit and the current worktree, after
|
|
166
|
+
# applying an optional command pipeline.
|
|
167
|
+
#
|
|
168
|
+
# Examples:
|
|
169
|
+
#
|
|
170
|
+
# # Compare the number of lines (`wc -l`) in file `foo` at the previous vs.
|
|
171
|
+
# current commit (`-r HEAD^..HEAD`):
|
|
172
|
+
#
|
|
173
|
+
# git diff-x -r HEAD^..HEAD wc -l foo
|
|
174
|
+
#
|
|
175
|
+
# # Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed
|
|
176
|
+
# value) vs. the current worktree content:
|
|
177
|
+
#
|
|
178
|
+
# git diff-x -c md5sum foo
|
|
179
|
+
#
|
|
180
|
+
# # Use `-` to separate pipeline commands from paths (when more than one path
|
|
181
|
+
# is to be diffed), e.g. this compares the largest 10 numbers in `file{1,2}`
|
|
182
|
+
# (HEAD vs. worktree):
|
|
183
|
+
#
|
|
184
|
+
# git diff-x 'sort -rn' head - file1 file2
|
|
185
|
+
#
|
|
186
|
+
# Options:
|
|
187
|
+
# -c, --color Colorize the output
|
|
188
|
+
# -r, --refspec TEXT <commit 1>..<commit 2> (compare two commits) or
|
|
189
|
+
# <commit> (compare <commit> to the worktree)
|
|
190
|
+
# -R, --ref TEXT Diff a specific commit; alias for `-r
|
|
191
|
+
# <ref>^..<ref>`
|
|
192
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
193
|
+
# to $SHELL
|
|
194
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
195
|
+
# `subprocess`es
|
|
196
|
+
# -U, --unified INTEGER Number of lines of context to show (passes
|
|
197
|
+
# through to `diff`)
|
|
198
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
199
|
+
# -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to
|
|
200
|
+
# `diff`)
|
|
201
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
202
|
+
# alternate syntax to passing commands as
|
|
203
|
+
# positional arguments
|
|
204
|
+
# --help Show this message and exit.
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Examples <a id="git-diff-x-examples"></a>
|
|
208
|
+
Compare line-count (`wc -l`) of this README, before and after commit `8b7a761`:
|
|
209
|
+
<!-- `bmdf -- git-diff-x -R 8b7a761 'wc -l' README.md` -->
|
|
210
|
+
```bash
|
|
211
|
+
git-diff-x -R 8b7a761 'wc -l' README.md
|
|
212
|
+
# 1c1
|
|
213
|
+
# < 16
|
|
214
|
+
# ---
|
|
215
|
+
# > 206
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Examples from `--help` above:
|
|
219
|
+
```bash
|
|
220
|
+
# Compare the number of lines (`wc -l`) in file `foo` at the previous vs. current commit
|
|
221
|
+
# (`-R HEAD` is equivalent to `-r HEAD^..HEAD`).
|
|
222
|
+
git diff-x -R HEAD wc -l foo
|
|
223
|
+
|
|
224
|
+
# Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed value) vs. the current
|
|
225
|
+
# worktree content.
|
|
226
|
+
git diff-x -c md5sum foo
|
|
227
|
+
|
|
228
|
+
# Use `-` to separate pipeline commands from paths (when more than one path is to be diffed),
|
|
229
|
+
# e.g. this compares the largest 10 numbers in `file{1,2}` (HEAD vs. worktree):
|
|
230
|
+
git diff-x 'sort -rn' head - file1 file2
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
I use `git-diff-x` via several Git and Bash aliases:
|
|
234
|
+
|
|
235
|
+
[`.gitconfig`][diff-x git configs]:
|
|
236
|
+
```.gitconfig
|
|
237
|
+
[alias]
|
|
238
|
+
; pip install dffs
|
|
239
|
+
dx = diff-x
|
|
240
|
+
dxc = diff-x -c
|
|
241
|
+
dxcr = diff-x -cR
|
|
242
|
+
dxcrr = diff-x -cr
|
|
243
|
+
dxr = diff-x -R
|
|
244
|
+
dxrr = diff-x -r
|
|
245
|
+
dxw = diff-x -w
|
|
246
|
+
dxwr = diff-x -wR
|
|
247
|
+
dxwrr = diff-x -wr
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
[`.git-rc`][diff-x aliases]:
|
|
251
|
+
```bash
|
|
252
|
+
alias gdx="g dx"
|
|
253
|
+
alias gdxc="g dxc"
|
|
254
|
+
alias gdxcr="g dxcr"
|
|
255
|
+
alias gdxcrr="g dxcrr"
|
|
256
|
+
alias gdxr="g dxr"
|
|
257
|
+
alias gdxrr="g dxrr"
|
|
258
|
+
alias gdxw="g dxw"
|
|
259
|
+
alias gdxwr="g dxwr"
|
|
260
|
+
alias gdxwrr="g dxwrr"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
[`jq`]: https://stedolan.github.io/jq/
|
|
264
|
+
[PyPI]: https://pypi.org/project/dffs/
|
|
265
|
+
|
|
266
|
+
[diff-x git configs]: https://github.com/ryan-williams/git-helpers/blob/5f27c2e4e88e3e14ede21483c998bdbe2cfccc6f/diff/.gitconfig#L64-L73
|
|
267
|
+
[diff-x aliases]: https://github.com/ryan-williams/git-helpers/blob/5f27c2e4e88e3e14ede21483c998bdbe2cfccc6f/diff/.git-rc#L59-L67
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .utils import join_pipelines, get_git_root, get_dir_path
|
dffs-0.0.5/dffs/cli.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from os import environ as env
|
|
2
|
+
|
|
3
|
+
from click import option, argument
|
|
4
|
+
|
|
5
|
+
shell_exec_opt = option('-s', '--shell-executable', help=f'Shell to use for executing commands; defaults to $SHELL')
|
|
6
|
+
no_shell_opt = option('-S', '--no-shell', is_flag=True, help="Don't pass `shell=True` to Python `subprocess`es")
|
|
7
|
+
verbose_opt = option('-v', '--verbose', is_flag=True, help="Log intermediate commands to stderr")
|
|
8
|
+
exec_cmd_opt = option('-x', '--exec-cmd', 'exec_cmds', multiple=True, help='Command(s) to execute before invoking `comm`; alternate syntax to passing commands as positional arguments')
|
|
9
|
+
args = argument('args', metavar='[exec_cmd...] <path1> <path2>', nargs=-1)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
from click import option, command
|
|
6
|
+
from utz import process
|
|
7
|
+
|
|
8
|
+
from dffs.cli import args, shell_exec_opt, no_shell_opt, verbose_opt, exec_cmd_opt
|
|
9
|
+
from dffs.utils import join_pipelines
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@command('comm-x', short_help='comm two files after running them through a pipeline of other commands', no_args_is_help=True)
|
|
13
|
+
@option('-1', '--exclude-1', is_flag=True, help='Exclude lines only found in the first pipeline')
|
|
14
|
+
@option('-2', '--exclude-2', is_flag=True, help='Exclude lines only found in the second pipeline')
|
|
15
|
+
@option('-3', '--exclude-3', is_flag=True, help='Exclude lines found in both pipelines')
|
|
16
|
+
@option('-i', '--case-insensitive', is_flag=True, help='Case insensitive comparison')
|
|
17
|
+
@shell_exec_opt
|
|
18
|
+
@no_shell_opt
|
|
19
|
+
@verbose_opt
|
|
20
|
+
@exec_cmd_opt
|
|
21
|
+
@args
|
|
22
|
+
def main(
|
|
23
|
+
exclude_1: bool,
|
|
24
|
+
exclude_2: bool,
|
|
25
|
+
exclude_3: bool,
|
|
26
|
+
case_insensitive: bool,
|
|
27
|
+
shell_executable: str | None,
|
|
28
|
+
no_shell: bool,
|
|
29
|
+
verbose: bool,
|
|
30
|
+
exec_cmds: Tuple[str, ...],
|
|
31
|
+
args: Tuple[str, ...],
|
|
32
|
+
):
|
|
33
|
+
"""Select or reject lines common to two input streams, after running each through a pipeline of other commands."""
|
|
34
|
+
if len(args) < 2:
|
|
35
|
+
raise ValueError('Must provide at least two files to comm')
|
|
36
|
+
|
|
37
|
+
*cmds, path1, path2 = args
|
|
38
|
+
cmds = list(exec_cmds) + cmds
|
|
39
|
+
if cmds:
|
|
40
|
+
first, *rest = cmds
|
|
41
|
+
join_pipelines(
|
|
42
|
+
base_cmd=[
|
|
43
|
+
'comm',
|
|
44
|
+
*(['-1'] if exclude_1 else []),
|
|
45
|
+
*(['-2'] if exclude_2 else []),
|
|
46
|
+
*(['-3'] if exclude_3 else []),
|
|
47
|
+
*(['-i'] if case_insensitive else []),
|
|
48
|
+
],
|
|
49
|
+
cmds1=[ f'{first} {path1}', *rest ],
|
|
50
|
+
cmds2=[ f'{first} {path2}', *rest ],
|
|
51
|
+
verbose=verbose,
|
|
52
|
+
shell=not no_shell,
|
|
53
|
+
executable=shell_executable,
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
process.run(['comm', path1, path2])
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
from click import option, command
|
|
7
|
+
|
|
8
|
+
from dffs.cli import args, shell_exec_opt, no_shell_opt, verbose_opt, exec_cmd_opt
|
|
9
|
+
from dffs.utils import join_pipelines
|
|
10
|
+
|
|
11
|
+
color_opt = option('-c', '--color', is_flag=True, help='Colorize the output')
|
|
12
|
+
unified_opt = option('-U', '--unified', type=int, help='Number of lines of context to show (passes through to `diff`)')
|
|
13
|
+
ignore_whitespace_opt = option('-w', '--ignore-whitespace', is_flag=True, help="Ignore whitespace differences (pass `-w` to `diff`)")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@command('diff-x', short_help='Diff two files after running them through a pipeline of other commands', no_args_is_help=True)
|
|
17
|
+
@color_opt
|
|
18
|
+
@shell_exec_opt
|
|
19
|
+
@no_shell_opt
|
|
20
|
+
@unified_opt
|
|
21
|
+
@verbose_opt
|
|
22
|
+
@ignore_whitespace_opt
|
|
23
|
+
@exec_cmd_opt
|
|
24
|
+
@args
|
|
25
|
+
def main(
|
|
26
|
+
color: bool,
|
|
27
|
+
shell_executable: str | None,
|
|
28
|
+
no_shell: bool,
|
|
29
|
+
unified: int | None,
|
|
30
|
+
verbose: bool,
|
|
31
|
+
ignore_whitespace: bool,
|
|
32
|
+
exec_cmds: Tuple[str, ...],
|
|
33
|
+
args: Tuple[str, ...],
|
|
34
|
+
):
|
|
35
|
+
"""Diff two files after running them through a pipeline of other commands."""
|
|
36
|
+
if len(args) < 2:
|
|
37
|
+
raise ValueError('Must provide at least two files to diff')
|
|
38
|
+
|
|
39
|
+
*cmds, path1, path2 = args
|
|
40
|
+
cmds = list(exec_cmds) + cmds
|
|
41
|
+
diff_args = [
|
|
42
|
+
*(['-w'] if ignore_whitespace else []),
|
|
43
|
+
*(['-U', str(unified)] if unified is not None else []),
|
|
44
|
+
*(['--color=always'] if color else []),
|
|
45
|
+
]
|
|
46
|
+
if cmds:
|
|
47
|
+
first, *rest = cmds
|
|
48
|
+
join_pipelines(
|
|
49
|
+
base_cmd=['diff', *diff_args],
|
|
50
|
+
cmds1=[ f'{first} {path1}', *rest ],
|
|
51
|
+
cmds2=[ f'{first} {path2}', *rest ],
|
|
52
|
+
verbose=verbose,
|
|
53
|
+
shell=not no_shell,
|
|
54
|
+
executable=shell_executable,
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
subprocess.run(['diff', *diff_args, path1, path2])
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
from click import option, argument, command
|
|
7
|
+
from utz import process, err
|
|
8
|
+
|
|
9
|
+
from dffs.cli import shell_exec_opt, no_shell_opt, verbose_opt, exec_cmd_opt
|
|
10
|
+
from dffs.diff_x import color_opt, unified_opt, ignore_whitespace_opt
|
|
11
|
+
from dffs.utils import join_pipelines
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@command('git-diff-x', short_help='Diff a Git-tracked file at two commits (or one commit vs. current worktree), optionally passing both through another command first')
|
|
15
|
+
@color_opt
|
|
16
|
+
@option('-r', '--refspec', help='<commit 1>..<commit 2> (compare two commits) or <commit> (compare <commit> to the worktree)')
|
|
17
|
+
@option('-R', '--ref', help="Diff a specific commit; alias for `-r <ref>^..<ref>`")
|
|
18
|
+
@shell_exec_opt
|
|
19
|
+
@no_shell_opt
|
|
20
|
+
@unified_opt
|
|
21
|
+
@verbose_opt
|
|
22
|
+
@ignore_whitespace_opt
|
|
23
|
+
@exec_cmd_opt
|
|
24
|
+
@argument('args', metavar='[exec_cmd...] [<path> | - [paths...]]', nargs=-1)
|
|
25
|
+
def main(
|
|
26
|
+
color: bool,
|
|
27
|
+
refspec: str | None,
|
|
28
|
+
ref: str | None,
|
|
29
|
+
shell_executable: str | None,
|
|
30
|
+
no_shell: bool,
|
|
31
|
+
unified: int | None,
|
|
32
|
+
verbose: bool,
|
|
33
|
+
ignore_whitespace: bool,
|
|
34
|
+
exec_cmds: Tuple[str, ...],
|
|
35
|
+
args: Tuple[str, ...],
|
|
36
|
+
):
|
|
37
|
+
"""Diff files at two commits, or one commit and the current worktree, after applying an optional command pipeline.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
|
|
41
|
+
# Compare the number of lines (`wc -l`) in file `foo` at the previous vs. current commit (`-r HEAD^..HEAD`):
|
|
42
|
+
|
|
43
|
+
git diff-x -r HEAD^..HEAD wc -l foo
|
|
44
|
+
|
|
45
|
+
# Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed value) vs. the current worktree content:
|
|
46
|
+
|
|
47
|
+
git diff-x -c md5sum foo
|
|
48
|
+
|
|
49
|
+
# Use `-` to separate pipeline commands from paths (when more than one path is to be diffed), e.g. this compares
|
|
50
|
+
the largest 10 numbers in `file{1,2}` (HEAD vs. worktree):
|
|
51
|
+
|
|
52
|
+
git diff-x 'sort -rn' head - file1 file2
|
|
53
|
+
"""
|
|
54
|
+
if '-' in args:
|
|
55
|
+
idx = args.index('-')
|
|
56
|
+
cmd_args = args[:idx]
|
|
57
|
+
paths = args[idx+1:]
|
|
58
|
+
else:
|
|
59
|
+
*cmd_args, path = args
|
|
60
|
+
paths = [path]
|
|
61
|
+
|
|
62
|
+
cmds = list(exec_cmds) + list(cmd_args)
|
|
63
|
+
shell = not no_shell
|
|
64
|
+
git_relpath_prefix = process.line('git', 'rev-parse', '--show-prefix', log=False)
|
|
65
|
+
|
|
66
|
+
if refspec and ref:
|
|
67
|
+
raise ValueError("Specify -r/--refspec xor -R/--ref")
|
|
68
|
+
if ref:
|
|
69
|
+
refspec = f'{ref}^..{ref}'
|
|
70
|
+
elif not refspec:
|
|
71
|
+
refspec = 'HEAD'
|
|
72
|
+
|
|
73
|
+
pcs = refspec.split('..', 1)
|
|
74
|
+
if len(pcs) == 1:
|
|
75
|
+
ref1 = pcs[0]
|
|
76
|
+
ref2 = None
|
|
77
|
+
elif len(pcs) == 2:
|
|
78
|
+
ref1, ref2 = pcs
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError(f"Invalid refspec: {refspec}")
|
|
81
|
+
|
|
82
|
+
diff_args = [
|
|
83
|
+
*(['-w'] if ignore_whitespace else []),
|
|
84
|
+
*(['-U', str(unified)] if unified is not None else []),
|
|
85
|
+
*(['--color=always'] if color else []),
|
|
86
|
+
]
|
|
87
|
+
for path in paths:
|
|
88
|
+
if len(paths) > 1:
|
|
89
|
+
err(path)
|
|
90
|
+
if cmds:
|
|
91
|
+
cmds1 = [ f'git show {ref1}:{git_relpath_prefix}{path}', *cmds ]
|
|
92
|
+
if ref2:
|
|
93
|
+
cmds2 = [ f'git show {ref2}:{git_relpath_prefix}{path}', *cmds ]
|
|
94
|
+
else:
|
|
95
|
+
cmd, *sub_cmds = cmds
|
|
96
|
+
cmds2 = [ f'{cmd} {path}', *sub_cmds ]
|
|
97
|
+
if not shell:
|
|
98
|
+
cmds1 = [ shlex.split(c) for c in cmds1 ]
|
|
99
|
+
cmds2 = [ shlex.split(c) for c in cmds2 ]
|
|
100
|
+
|
|
101
|
+
join_pipelines(
|
|
102
|
+
base_cmd=['diff', *diff_args],
|
|
103
|
+
cmds1=cmds1,
|
|
104
|
+
cmds2=cmds2,
|
|
105
|
+
verbose=verbose,
|
|
106
|
+
shell=not no_shell,
|
|
107
|
+
executable=shell_executable,
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
process.run(['git', 'diff', *diff_args, refspec, '--', path])
|
dffs-0.0.5/dffs/utils.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import cache
|
|
4
|
+
from os import environ as env, getcwd
|
|
5
|
+
from os.path import relpath
|
|
6
|
+
from subprocess import Popen
|
|
7
|
+
|
|
8
|
+
from utz import err, named_pipes, pipeline, process
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@cache
|
|
12
|
+
def get_git_root() -> str:
|
|
13
|
+
return process.line('git', 'rev-parse', '--show-toplevel', log=False)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cache
|
|
17
|
+
def get_dir_path() -> str:
|
|
18
|
+
return relpath(getcwd(), get_git_root())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def join_pipelines(
|
|
22
|
+
base_cmd: list[str],
|
|
23
|
+
cmds1: list[str],
|
|
24
|
+
cmds2: list[str],
|
|
25
|
+
verbose: bool = False,
|
|
26
|
+
executable: str | None = None,
|
|
27
|
+
**kwargs,
|
|
28
|
+
):
|
|
29
|
+
"""Run two sequences of piped commands, pass their outputs as inputs to a ``base_cmd``.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
base_cmd: Top=level command that takes two positional args (named pipes with the outputs
|
|
33
|
+
of the ``cmds1`` and ``cmds2`` pipelines).
|
|
34
|
+
cmds1: First sequence of commands to pipe together
|
|
35
|
+
cmds2: Second sequence of commands to pipe together
|
|
36
|
+
verbose: Whether to print commands being executed
|
|
37
|
+
executable: Shell to use for executing commands; defaults to $SHELL
|
|
38
|
+
**kwargs: Additional arguments passed to subprocess.Popen
|
|
39
|
+
|
|
40
|
+
Each command sequence will be piped together before being compared.
|
|
41
|
+
For example, if cmds1 = ['cat foo.txt', 'sort'], the function will
|
|
42
|
+
execute 'cat foo.txt | sort' before comparing with cmds2's output.
|
|
43
|
+
|
|
44
|
+
Adapted from https://stackoverflow.com/a/28840955"""
|
|
45
|
+
if executable is None:
|
|
46
|
+
executable = env.get('SHELL')
|
|
47
|
+
|
|
48
|
+
with named_pipes(n=2) as pipes:
|
|
49
|
+
(pipe1, pipe2) = pipes
|
|
50
|
+
join_cmd = [
|
|
51
|
+
*base_cmd,
|
|
52
|
+
pipe1,
|
|
53
|
+
pipe2,
|
|
54
|
+
]
|
|
55
|
+
proc = Popen(join_cmd)
|
|
56
|
+
processes = [proc]
|
|
57
|
+
|
|
58
|
+
for pipe, cmds in ((pipe1, cmds1), (pipe2, cmds2)):
|
|
59
|
+
if verbose:
|
|
60
|
+
err(f"Running pipeline: {' | '.join(cmds)}")
|
|
61
|
+
|
|
62
|
+
processes += pipeline(
|
|
63
|
+
cmds,
|
|
64
|
+
pipe,
|
|
65
|
+
wait=False,
|
|
66
|
+
executable=executable,
|
|
67
|
+
**kwargs,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
for p in processes:
|
|
71
|
+
p.wait()
|
|
72
|
+
|
|
73
|
+
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dffs
|
|
3
|
+
Version: 0.0.5
|
|
4
|
+
Summary: Pipe and diff files: execute shell pipelines against multiple inputs, diff/compare/join results.
|
|
5
|
+
Home-page: https://github.com/runsascoded/dffs
|
|
6
|
+
Author: Ryan Williams
|
|
7
|
+
Author-email: ryan@runsascoded.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
# dffs
|
|
13
|
+
Pipe and diff files: execute shell pipelines against multiple inputs, diff/compare/join results.
|
|
14
|
+
|
|
15
|
+
[][PyPI]
|
|
16
|
+
<!-- toc -->
|
|
17
|
+
- [Install](#install)
|
|
18
|
+
- [CLIs](#CLIs)
|
|
19
|
+
- [`diff-x`](#diff-x)
|
|
20
|
+
- [Usage](#diff-x-usage)
|
|
21
|
+
- [Examples](#diff-x-examples)
|
|
22
|
+
- [`comm-x`](#comm-x)
|
|
23
|
+
- [Usage](#comm-x-usage)
|
|
24
|
+
- [Examples](#comm-x-examples)
|
|
25
|
+
- [`git-diff-x`](#git-diff-x)
|
|
26
|
+
- [Usage](#git-diff-x-usage)
|
|
27
|
+
- [Examples](#git-diff-x-examples)
|
|
28
|
+
<!-- /toc -->
|
|
29
|
+
|
|
30
|
+
## Install <a id="install"></a>
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install dffs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## CLIs <a id="CLIs"></a>
|
|
37
|
+
|
|
38
|
+
### `diff-x` <a id="diff-x"></a>
|
|
39
|
+
|
|
40
|
+
#### Usage <a id="diff-x-usage"></a>
|
|
41
|
+
<!-- `bmdf diff-x` -->
|
|
42
|
+
```bash
|
|
43
|
+
diff-x
|
|
44
|
+
# Usage: diff-x [OPTIONS] [exec_cmd...] <path1> <path2>
|
|
45
|
+
#
|
|
46
|
+
# Diff two files after running them through a pipeline of other commands.
|
|
47
|
+
#
|
|
48
|
+
# Options:
|
|
49
|
+
# -c, --color Colorize the output
|
|
50
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
51
|
+
# to $SHELL
|
|
52
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
53
|
+
# `subprocess`es
|
|
54
|
+
# -U, --unified INTEGER Number of lines of context to show (passes
|
|
55
|
+
# through to `diff`)
|
|
56
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
57
|
+
# -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to
|
|
58
|
+
# `diff`)
|
|
59
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
60
|
+
# alternate syntax to passing commands as
|
|
61
|
+
# positional arguments
|
|
62
|
+
# --help Show this message and exit.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Examples <a id="diff-x-examples"></a>
|
|
66
|
+
|
|
67
|
+
Given two similar JSON objects, where one is compact and the other is pretty-printed:
|
|
68
|
+
```bash
|
|
69
|
+
echo '{"a":1,"b":2}' > 1.json
|
|
70
|
+
echo '{"a":1,"b":3}' | jq > 2.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`diff {1,2}.json` outputs the entirety of both objects:
|
|
74
|
+
```diff
|
|
75
|
+
1c1,4
|
|
76
|
+
< {"a":1,"b":2}
|
|
77
|
+
---
|
|
78
|
+
> {
|
|
79
|
+
> "a": 1,
|
|
80
|
+
> "b": 3
|
|
81
|
+
> }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`diff-x 'jq .' {1,2}.json` pretty-prints each side before `diff`ing:
|
|
85
|
+
```diff
|
|
86
|
+
3c3
|
|
87
|
+
< "b": 2
|
|
88
|
+
---
|
|
89
|
+
> "b": 3
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `comm-x` <a id="comm-x"></a>
|
|
93
|
+
`comm` essentially performs set intersection/difference; `comm-x` allows you to run a pipeline of commands on each input, before comparing them.
|
|
94
|
+
|
|
95
|
+
#### Usage <a id="comm-x-usage"></a>
|
|
96
|
+
<!-- `bmdf comm-x` -->
|
|
97
|
+
```bash
|
|
98
|
+
comm-x
|
|
99
|
+
# Usage: comm-x [OPTIONS] [exec_cmd...] <path1> <path2>
|
|
100
|
+
#
|
|
101
|
+
# Select or reject lines common to two input streams, after running each
|
|
102
|
+
# through a pipeline of other commands.
|
|
103
|
+
#
|
|
104
|
+
# Options:
|
|
105
|
+
# -1, --exclude-1 Exclude lines only found in the first pipeline
|
|
106
|
+
# -2, --exclude-2 Exclude lines only found in the second pipeline
|
|
107
|
+
# -3, --exclude-3 Exclude lines found in both pipelines
|
|
108
|
+
# -i, --case-insensitive Case insensitive comparison
|
|
109
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
110
|
+
# to $SHELL
|
|
111
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
112
|
+
# `subprocess`es
|
|
113
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
114
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
115
|
+
# alternate syntax to passing commands as
|
|
116
|
+
# positional arguments
|
|
117
|
+
# --help Show this message and exit.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Examples <a id="comm-x-examples"></a>
|
|
121
|
+
Given two similar lists of numbers, but in different orders:
|
|
122
|
+
```bash
|
|
123
|
+
seq 10 > 1.txt
|
|
124
|
+
seq 10 -2 0 > 2.txt
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`comm` outputs gibberish, because the files aren't in sorted order:
|
|
128
|
+
<!-- `bmdf comm 1.txt 2.txt` -->
|
|
129
|
+
```bash
|
|
130
|
+
comm 1.txt 2.txt
|
|
131
|
+
# 1
|
|
132
|
+
# 10
|
|
133
|
+
# 2
|
|
134
|
+
# 3
|
|
135
|
+
# 4
|
|
136
|
+
# 5
|
|
137
|
+
# 6
|
|
138
|
+
# 7
|
|
139
|
+
# 8
|
|
140
|
+
# comm: file 2 is not in sorted order
|
|
141
|
+
# 6
|
|
142
|
+
# 4
|
|
143
|
+
# 2
|
|
144
|
+
# 0
|
|
145
|
+
# 9
|
|
146
|
+
# comm: file 1 is not in sorted order
|
|
147
|
+
# 10
|
|
148
|
+
# comm: input is not in sorted order
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`comm-x sort` sorts each file first:
|
|
152
|
+
<!-- `bmdf comm-x sort 1.txt 2.txt` -->
|
|
153
|
+
```bash
|
|
154
|
+
comm-x sort 1.txt 2.txt
|
|
155
|
+
# 0
|
|
156
|
+
# 1
|
|
157
|
+
# 10
|
|
158
|
+
# 2
|
|
159
|
+
# 3
|
|
160
|
+
# 4
|
|
161
|
+
# 5
|
|
162
|
+
# 6
|
|
163
|
+
# 7
|
|
164
|
+
# 8
|
|
165
|
+
# 9
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `git-diff-x` <a id="git-diff-x"></a>
|
|
169
|
+
|
|
170
|
+
#### Usage <a id="git-diff-x-usage"></a>
|
|
171
|
+
<!-- `bmdf -- git-diff-x --help` -->
|
|
172
|
+
```bash
|
|
173
|
+
git-diff-x --help
|
|
174
|
+
# Usage: git-diff-x [OPTIONS] [exec_cmd...] [<path> | - [paths...]]
|
|
175
|
+
#
|
|
176
|
+
# Diff files at two commits, or one commit and the current worktree, after
|
|
177
|
+
# applying an optional command pipeline.
|
|
178
|
+
#
|
|
179
|
+
# Examples:
|
|
180
|
+
#
|
|
181
|
+
# # Compare the number of lines (`wc -l`) in file `foo` at the previous vs.
|
|
182
|
+
# current commit (`-r HEAD^..HEAD`):
|
|
183
|
+
#
|
|
184
|
+
# git diff-x -r HEAD^..HEAD wc -l foo
|
|
185
|
+
#
|
|
186
|
+
# # Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed
|
|
187
|
+
# value) vs. the current worktree content:
|
|
188
|
+
#
|
|
189
|
+
# git diff-x -c md5sum foo
|
|
190
|
+
#
|
|
191
|
+
# # Use `-` to separate pipeline commands from paths (when more than one path
|
|
192
|
+
# is to be diffed), e.g. this compares the largest 10 numbers in `file{1,2}`
|
|
193
|
+
# (HEAD vs. worktree):
|
|
194
|
+
#
|
|
195
|
+
# git diff-x 'sort -rn' head - file1 file2
|
|
196
|
+
#
|
|
197
|
+
# Options:
|
|
198
|
+
# -c, --color Colorize the output
|
|
199
|
+
# -r, --refspec TEXT <commit 1>..<commit 2> (compare two commits) or
|
|
200
|
+
# <commit> (compare <commit> to the worktree)
|
|
201
|
+
# -R, --ref TEXT Diff a specific commit; alias for `-r
|
|
202
|
+
# <ref>^..<ref>`
|
|
203
|
+
# -s, --shell-executable TEXT Shell to use for executing commands; defaults
|
|
204
|
+
# to $SHELL
|
|
205
|
+
# -S, --no-shell Don't pass `shell=True` to Python
|
|
206
|
+
# `subprocess`es
|
|
207
|
+
# -U, --unified INTEGER Number of lines of context to show (passes
|
|
208
|
+
# through to `diff`)
|
|
209
|
+
# -v, --verbose Log intermediate commands to stderr
|
|
210
|
+
# -w, --ignore-whitespace Ignore whitespace differences (pass `-w` to
|
|
211
|
+
# `diff`)
|
|
212
|
+
# -x, --exec-cmd TEXT Command(s) to execute before invoking `comm`;
|
|
213
|
+
# alternate syntax to passing commands as
|
|
214
|
+
# positional arguments
|
|
215
|
+
# --help Show this message and exit.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Examples <a id="git-diff-x-examples"></a>
|
|
219
|
+
Compare line-count (`wc -l`) of this README, before and after commit `8b7a761`:
|
|
220
|
+
<!-- `bmdf -- git-diff-x -R 8b7a761 'wc -l' README.md` -->
|
|
221
|
+
```bash
|
|
222
|
+
git-diff-x -R 8b7a761 'wc -l' README.md
|
|
223
|
+
# 1c1
|
|
224
|
+
# < 16
|
|
225
|
+
# ---
|
|
226
|
+
# > 206
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Examples from `--help` above:
|
|
230
|
+
```bash
|
|
231
|
+
# Compare the number of lines (`wc -l`) in file `foo` at the previous vs. current commit
|
|
232
|
+
# (`-R HEAD` is equivalent to `-r HEAD^..HEAD`).
|
|
233
|
+
git diff-x -R HEAD wc -l foo
|
|
234
|
+
|
|
235
|
+
# Colorized (`-c`) diff of `md5sum`s of `foo`, at HEAD (last committed value) vs. the current
|
|
236
|
+
# worktree content.
|
|
237
|
+
git diff-x -c md5sum foo
|
|
238
|
+
|
|
239
|
+
# Use `-` to separate pipeline commands from paths (when more than one path is to be diffed),
|
|
240
|
+
# e.g. this compares the largest 10 numbers in `file{1,2}` (HEAD vs. worktree):
|
|
241
|
+
git diff-x 'sort -rn' head - file1 file2
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
I use `git-diff-x` via several Git and Bash aliases:
|
|
245
|
+
|
|
246
|
+
[`.gitconfig`][diff-x git configs]:
|
|
247
|
+
```.gitconfig
|
|
248
|
+
[alias]
|
|
249
|
+
; pip install dffs
|
|
250
|
+
dx = diff-x
|
|
251
|
+
dxc = diff-x -c
|
|
252
|
+
dxcr = diff-x -cR
|
|
253
|
+
dxcrr = diff-x -cr
|
|
254
|
+
dxr = diff-x -R
|
|
255
|
+
dxrr = diff-x -r
|
|
256
|
+
dxw = diff-x -w
|
|
257
|
+
dxwr = diff-x -wR
|
|
258
|
+
dxwrr = diff-x -wr
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
[`.git-rc`][diff-x aliases]:
|
|
262
|
+
```bash
|
|
263
|
+
alias gdx="g dx"
|
|
264
|
+
alias gdxc="g dxc"
|
|
265
|
+
alias gdxcr="g dxcr"
|
|
266
|
+
alias gdxcrr="g dxcrr"
|
|
267
|
+
alias gdxr="g dxr"
|
|
268
|
+
alias gdxrr="g dxrr"
|
|
269
|
+
alias gdxw="g dxw"
|
|
270
|
+
alias gdxwr="g dxwr"
|
|
271
|
+
alias gdxwrr="g dxwrr"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
[`jq`]: https://stedolan.github.io/jq/
|
|
275
|
+
[PyPI]: https://pypi.org/project/dffs/
|
|
276
|
+
|
|
277
|
+
[diff-x git configs]: https://github.com/ryan-williams/git-helpers/blob/5f27c2e4e88e3e14ede21483c998bdbe2cfccc6f/diff/.gitconfig#L64-L73
|
|
278
|
+
[diff-x aliases]: https://github.com/ryan-williams/git-helpers/blob/5f27c2e4e88e3e14ede21483c998bdbe2cfccc6f/diff/.git-rc#L59-L67
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
dffs/__init__.py
|
|
5
|
+
dffs/cli.py
|
|
6
|
+
dffs/utils.py
|
|
7
|
+
dffs.egg-info/PKG-INFO
|
|
8
|
+
dffs.egg-info/SOURCES.txt
|
|
9
|
+
dffs.egg-info/dependency_links.txt
|
|
10
|
+
dffs.egg-info/entry_points.txt
|
|
11
|
+
dffs.egg-info/requires.txt
|
|
12
|
+
dffs.egg-info/top_level.txt
|
|
13
|
+
dffs/comm_x/__init__.py
|
|
14
|
+
dffs/diff_x/__init__.py
|
|
15
|
+
dffs/git_diff_x/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dffs
|
dffs-0.0.5/setup.cfg
ADDED
dffs-0.0.5/setup.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='dffs',
|
|
5
|
+
version="0.0.5",
|
|
6
|
+
description="Pipe and diff files: execute shell pipelines against multiple inputs, diff/compare/join results.",
|
|
7
|
+
long_description=open("README.md").read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
packages=find_packages(),
|
|
10
|
+
install_requires=open("requirements.txt").read(),
|
|
11
|
+
entry_points={
|
|
12
|
+
'console_scripts': [
|
|
13
|
+
'diff-x = dffs.diff_x:main',
|
|
14
|
+
'comm-x = dffs.comm_x:main',
|
|
15
|
+
'git-diff-x = dffs.git_diff_x:main',
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
license="MIT",
|
|
19
|
+
author="Ryan Williams",
|
|
20
|
+
author_email="ryan@runsascoded.com",
|
|
21
|
+
author_url="https://github.com/ryan-williams",
|
|
22
|
+
url="https://github.com/runsascoded/dffs",
|
|
23
|
+
)
|