Coverage for pyguymer3/save_file_if_needed.py: 2%
41 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def save_file_if_needed(
5 fname,
6 fcontent,
7 /,
8 *,
9 debug = __debug__,
10 gitFiles = None,
11 gitMessage = "regenerated",
12 gitPath = None,
13 timeout = 60.0,
14):
15 """Save a file. If the file already exists, then only overwrite it if the
16 content is different.
18 Parameters
19 ----------
20 fname : str
21 the file name
22 fcontent : bytes or str
23 the file content
24 debug : bool, optional
25 print debug messages
26 gitFiles : list of str, optional
27 a list of file names known to Git, if the file ends up being saved and
28 the file is not already known to Git then it will be added to Git
29 gitMessage : str, optional
30 the Git commit message, if the file ends up being saved and then commit
31 it to Git
32 gitPath : str, optional
33 the path to the "git" binary (if not provided then Python will attempt
34 to find the binary itself)
35 timeout : float, optional
36 the timeout for any requests/subprocess calls
38 Returns
39 -------
40 needsSaving : bool
41 did the file need saving
43 Notes
44 -----
45 Copyright 2017 Thomas Guymer [1]_
47 References
48 ----------
49 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3
50 """
52 # Import standard modules ...
53 import os
54 import shutil
55 import subprocess
57 # **************************************************************************
59 # Try to find the paths if the user did not provide them ...
60 if gitPath is None:
61 gitPath = shutil.which("git")
62 assert gitPath is not None, "\"git\" is not installed"
64 # Check that the content is one of the two types allowed in Python 3 and set
65 # the file access mode and the encoding ...
66 match fcontent:
67 case bytes():
68 mode = "b"
69 encoding = None
70 case str():
71 mode = "t"
72 encoding = "utf-8"
73 case _:
74 # Crash ...
75 raise TypeError(f"\"fcontent\" is an unexpected type ({repr(type(fcontent))})") from None
77 # Initialize trigger ...
78 needsSaving = False
80 # Check if the file exists ...
81 if os.path.exists(fname):
82 # Open the file ...
83 with open(fname, f"r{mode}", encoding = encoding) as fObj:
84 # Check if the content is the same ...
85 if fObj.read() != fcontent:
86 # Set trigger ...
87 needsSaving = True
88 else:
89 # Set trigger ...
90 needsSaving = True
92 # Create short-hand for the parent directory ...
93 dname = os.path.dirname(fname)
95 # Check that there is a parent directory in the provided file name path ...
96 if dname != "":
97 # Check if the parent directory does not exist ...
98 if not os.path.exists(dname):
99 # Make the parent directory ...
100 os.makedirs(dname)
102 # Check if the file needs saving ...
103 if needsSaving:
104 if debug:
105 print(f"INFO: Saving \"{fname}\" ...")
107 # Open the file ...
108 with open(fname, f"w{mode}", encoding = encoding) as fObj:
109 # Save the file ...
110 fObj.write(fcontent)
112 # Check if the user provided a list of files known to Git ...
113 if gitFiles is not None:
114 # Check if the file is not known to Git ...
115 if fname not in gitFiles:
116 if debug:
117 print(f"INFO: Adding \"{fname}\" to Git ...")
119 # Add the file to Git ...
120 subprocess.run(
121 [
122 gitPath,
123 "add",
124 "--intent-to-add",
125 fname,
126 ],
127 check = True,
128 encoding = "utf-8",
129 stderr = subprocess.DEVNULL,
130 stdout = subprocess.DEVNULL,
131 timeout = timeout,
132 )
134 # Check if the user provided a commit message ...
135 if gitMessage is not None:
136 if debug:
137 print(f"INFO: Committing \"{fname}\" to Git ...")
139 # Commit the file to Git ...
140 subprocess.run(
141 [
142 gitPath,
143 "commit",
144 fname,
145 "-m", gitMessage,
146 ],
147 check = True,
148 encoding = "utf-8",
149 stderr = subprocess.DEVNULL,
150 stdout = subprocess.DEVNULL,
151 timeout = timeout,
152 )
154 # Return answer ...
155 return needsSaving