summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/jsonpatch72
1 files changed, 68 insertions, 4 deletions
diff --git a/bin/jsonpatch b/bin/jsonpatch
index 8db17a1..3f01738 100755
--- a/bin/jsonpatch
+++ b/bin/jsonpatch
@@ -1,12 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from __future__ import print_function
-
import sys
import os.path
import json
import jsonpatch
+import tempfile
import argparse
@@ -15,9 +14,14 @@ parser = argparse.ArgumentParser(
parser.add_argument('ORIGINAL', type=argparse.FileType('r'),
help='Original file')
parser.add_argument('PATCH', type=argparse.FileType('r'),
- help='Patch file')
+ nargs='?', default=sys.stdin,
+ help='Patch file (read from stdin if omitted)')
parser.add_argument('--indent', type=int, default=None,
help='Indent output by n spaces')
+parser.add_argument('-b', '--backup', action='store_true',
+ help='Back up ORIGINAL if modifying in-place')
+parser.add_argument('-i', '--in-place', action='store_true',
+ help='Modify ORIGINAL in-place instead of to stdout')
parser.add_argument('-v', '--version', action='version',
version='%(prog)s ' + jsonpatch.__version__)
@@ -35,7 +39,67 @@ def patch_files():
doc = json.load(args.ORIGINAL)
patch = json.load(args.PATCH)
result = jsonpatch.apply_patch(doc, patch)
- print(json.dumps(result, indent=args.indent))
+
+ if args.in_place:
+ dirname = os.path.abspath(os.path.dirname(args.ORIGINAL.name))
+
+ try:
+ # Attempt to replace the file atomically. We do this by
+ # creating a temporary file in the same directory as the
+ # original file so we can atomically move the new file over
+ # the original later. (This is done in the same directory
+ # because atomic renames do not work across mount points.)
+
+ fd, pathname = tempfile.mkstemp(dir=dirname)
+ fp = os.fdopen(fd, 'w')
+ atomic = True
+
+ except OSError:
+ # We failed to create the temporary file for an atomic
+ # replace, so fall back to non-atomic mode by backing up
+ # the original (if desired) and writing a new file.
+
+ if args.backup:
+ os.rename(args.ORIGINAL.name, args.ORIGINAL.name + '.orig')
+ fp = open(args.ORIGINAL.name, 'w')
+ atomic = False
+
+ else:
+ # Since we're not replacing the original file in-place, write
+ # the modified JSON to stdout instead.
+
+ fp = sys.stdout
+
+ # By this point we have some sort of file object we can write the
+ # modified JSON to.
+
+ json.dump(result, fp, indent=args.indent)
+ fp.write('\n')
+
+ if args.in_place:
+ # Close the new file. If we aren't replacing atomically, this
+ # is our last step, since everything else is already in place.
+
+ fp.close()
+
+ if atomic:
+ try:
+ # Complete the atomic replace by linking the original
+ # to a backup (if desired), fixing up the permissions
+ # on the temporary file, and moving it into place.
+
+ if args.backup:
+ os.link(args.ORIGINAL.name, args.ORIGINAL.name + '.orig')
+ os.chmod(pathname, os.stat(args.ORIGINAL.name).st_mode)
+ os.rename(pathname, args.ORIGINAL.name)
+
+ except OSError:
+ # In the event we could not actually do the atomic
+ # replace, unlink the original to move it out of the
+ # way and finally move the temporary file into place.
+
+ os.unlink(args.ORIGINAL.name)
+ os.rename(pathname, args.ORIGINAL.name)
if __name__ == "__main__":