Skip to content

Commit bdcfb26

Browse files
committed
Merge pull request #20 from zigg/master
add in-place patching and patch read from stdin. enables use of
2 parents 8f7854f + d0ec269 commit bdcfb26

File tree

1 file changed

+68
-4
lines changed

1 file changed

+68
-4
lines changed

bin/jsonpatch

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4-
from __future__ import print_function
5-
64
import sys
75
import os.path
86
import json
97
import jsonpatch
8+
import tempfile
109
import argparse
1110

1211

@@ -15,9 +14,14 @@ parser = argparse.ArgumentParser(
1514
parser.add_argument('ORIGINAL', type=argparse.FileType('r'),
1615
help='Original file')
1716
parser.add_argument('PATCH', type=argparse.FileType('r'),
18-
help='Patch file')
17+
nargs='?', default=sys.stdin,
18+
help='Patch file (read from stdin if omitted)')
1919
parser.add_argument('--indent', type=int, default=None,
2020
help='Indent output by n spaces')
21+
parser.add_argument('-b', '--backup', action='store_true',
22+
help='Back up ORIGINAL if modifying in-place')
23+
parser.add_argument('-i', '--in-place', action='store_true',
24+
help='Modify ORIGINAL in-place instead of to stdout')
2125
parser.add_argument('-v', '--version', action='version',
2226
version='%(prog)s ' + jsonpatch.__version__)
2327

@@ -35,7 +39,67 @@ def patch_files():
3539
doc = json.load(args.ORIGINAL)
3640
patch = json.load(args.PATCH)
3741
result = jsonpatch.apply_patch(doc, patch)
38-
print(json.dumps(result, indent=args.indent))
42+
43+
if args.in_place:
44+
dirname = os.path.abspath(os.path.dirname(args.ORIGINAL.name))
45+
46+
try:
47+
# Attempt to replace the file atomically. We do this by
48+
# creating a temporary file in the same directory as the
49+
# original file so we can atomically move the new file over
50+
# the original later. (This is done in the same directory
51+
# because atomic renames do not work across mount points.)
52+
53+
fd, pathname = tempfile.mkstemp(dir=dirname)
54+
fp = os.fdopen(fd, 'w')
55+
atomic = True
56+
57+
except OSError:
58+
# We failed to create the temporary file for an atomic
59+
# replace, so fall back to non-atomic mode by backing up
60+
# the original (if desired) and writing a new file.
61+
62+
if args.backup:
63+
os.rename(args.ORIGINAL.name, args.ORIGINAL.name + '.orig')
64+
fp = open(args.ORIGINAL.name, 'w')
65+
atomic = False
66+
67+
else:
68+
# Since we're not replacing the original file in-place, write
69+
# the modified JSON to stdout instead.
70+
71+
fp = sys.stdout
72+
73+
# By this point we have some sort of file object we can write the
74+
# modified JSON to.
75+
76+
json.dump(result, fp, indent=args.indent)
77+
fp.write('\n')
78+
79+
if args.in_place:
80+
# Close the new file. If we aren't replacing atomically, this
81+
# is our last step, since everything else is already in place.
82+
83+
fp.close()
84+
85+
if atomic:
86+
try:
87+
# Complete the atomic replace by linking the original
88+
# to a backup (if desired), fixing up the permissions
89+
# on the temporary file, and moving it into place.
90+
91+
if args.backup:
92+
os.link(args.ORIGINAL.name, args.ORIGINAL.name + '.orig')
93+
os.chmod(pathname, os.stat(args.ORIGINAL.name).st_mode)
94+
os.rename(pathname, args.ORIGINAL.name)
95+
96+
except OSError:
97+
# In the event we could not actually do the atomic
98+
# replace, unlink the original to move it out of the
99+
# way and finally move the temporary file into place.
100+
101+
os.unlink(args.ORIGINAL.name)
102+
os.rename(pathname, args.ORIGINAL.name)
39103

40104

41105
if __name__ == "__main__":

0 commit comments

Comments
 (0)