@@ -14,14 +14,14 @@ parser = argparse.ArgumentParser(
14
14
parser .add_argument ('ORIGINAL' , type = argparse .FileType ('r' ),
15
15
help = 'Original file' )
16
16
parser .add_argument ('PATCH' , type = argparse .FileType ('r' ),
17
- nargs = '?' , default = sys .stdin ,
17
+ nargs = '?' , default = sys .stdin ,
18
18
help = 'Patch file (read from stdin if omitted)' )
19
19
parser .add_argument ('--indent' , type = int , default = None ,
20
20
help = 'Indent output by n spaces' )
21
21
parser .add_argument ('-b' , '--backup' , action = 'store_true' ,
22
- help = 'Back up ORIGINAL if modifying in-place' )
22
+ help = 'Back up ORIGINAL if modifying in-place' )
23
23
parser .add_argument ('-i' , '--in-place' , action = 'store_true' ,
24
- help = 'Modify ORIGINAL in-place instead of to stdout' )
24
+ help = 'Modify ORIGINAL in-place instead of to stdout' )
25
25
parser .add_argument ('-v' , '--version' , action = 'version' ,
26
26
version = '%(prog)s ' + jsonpatch .__version__ )
27
27
@@ -42,31 +42,61 @@ def patch_files():
42
42
43
43
if args .in_place :
44
44
dirname = os .path .abspath (os .path .dirname (args .ORIGINAL .name ))
45
+
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.
51
+
46
52
fd , pathname = tempfile .mkstemp (dir = dirname )
47
53
fp = os .fdopen (fd , 'w' )
48
54
atomic = True
55
+
49
56
except OSError :
57
+ # We failed to create the temporary file for an atomic
58
+ # replace, so fall back to non-atomic mode by backing up
59
+ # the original (if desired) and writing a new file.
60
+
50
61
if args .backup :
51
62
os .rename (args .ORIGINAL .name , args .ORIGINAL .name + '.orig' )
52
63
fp = open (args .ORIGINAL .name , 'w' )
53
64
atomic = False
54
65
55
66
else :
67
+ # Since we're not replacing the original file in-place, write
68
+ # the modified JSON to stdout instead.
69
+
56
70
fp = sys .stdout
57
71
72
+ # By this point we have some sort of file object we can write the
73
+ # modified JSON to.
74
+
58
75
json .dump (result , fp , indent = args .indent )
59
76
fp .write ('\n ' )
60
77
61
78
if args .in_place :
79
+ # Close the new file. If we aren't replacing atomically, this
80
+ # is our last step, since everything else is already in place.
81
+
62
82
fp .close ()
83
+
63
84
if atomic :
64
85
try :
86
+ # Complete the atomic replace by linking the original
87
+ # to a backup (if desired), fixing up the permissions
88
+ # on the temporary file, and moving it into place.
89
+
65
90
if args .backup :
66
91
os .link (args .ORIGINAL .name , args .ORIGINAL .name + '.orig' )
67
92
os .chmod (pathname , os .stat (args .ORIGINAL .name ).st_mode )
68
93
os .rename (pathname , args .ORIGINAL .name )
94
+
69
95
except OSError :
96
+ # In the event we could not actually do the atomic
97
+ # replace, unlink the original to move it out of the
98
+ # way and finally move the temporary file into place.
99
+
70
100
os .unlink (args .ORIGINAL .name )
71
101
os .rename (pathname , args .ORIGINAL .name )
72
102
0 commit comments