-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathiniparser.py
More file actions
337 lines (243 loc) · 10.8 KB
/
iniparser.py
File metadata and controls
337 lines (243 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
import os
'''
Github: https://github.com/Greenest-Guy
VARIABLE NAMING
file_path --> Complete file path to a skin.ini file
content --> Complete contents of a skin.ini file
dir_path --> Path to a directory
'''
class IniParser:
# returns contents of file at file_path
@staticmethod
def getContents(file_path):
with open(file_path, 'r', encoding="utf-8") as file:
content = file.read()
return content
# returns a list of lines from the file at file_path
@staticmethod
def getLines(file_path):
with open(file_path, 'r', encoding="utf-8") as file:
content = file.readlines()
return content
# returns the line at index n (1-based) from file_path (skin.ini)
@staticmethod
def getLine(file_path, index):
with open(file_path, 'r', encoding="utf-8") as file:
content = file.readlines()
if index < 1 or index > len(content):
raise IndexError("Line index out of range.")
return content[index - 1]
# returns true or false by seeing if line starts with starting (case-insensitive and clears whitespace)
@staticmethod
def startsWith(line, starting: str):
return line.lower().strip().startswith(starting.lower())
# returns complete path to the skin.ini within a directory (non-recursive)
@staticmethod
def findSkinini(dir_path):
try:
for file in os.listdir(dir_path):
if os.path.isfile(os.path.join(dir_path, file)) and file.lower() == "skin.ini":
return os.path.join(dir_path, file)
except Exception:
return None
# returns the value from the key:value pair in line
@staticmethod
def getValueFromLine(line):
if ':' not in line:
return None
return line[(line.index(':')+1):].strip()
# returns the key from the key:value pair in line
@staticmethod
def getKeyFromLine(line):
if ':' not in line:
return None
return line.split(':', 1)[0].strip()
# returns the first instance of a value associated with key
@staticmethod
def getValue(file_path, key):
for line in IniParser.getLines(file_path):
if IniParser.startsWith(line, f"{key}:"):
return IniParser.getValueFromLine(line)
# returns keycount associated with a section
@staticmethod
def getSectionKeycount(section):
for line in section.splitlines():
if IniParser.startsWith(line, "keys:"):
return IniParser.getValueFromLine(line)
# returns a list of all categories within a skin.ini file
@staticmethod
def getCategories(file_path):
categories = []
with open(file_path, 'r', encoding="utf-8") as file:
content = file.readlines()
for line in content:
line = line.strip()
if IniParser.startsWith(line, '[') and line.endswith(']') and line not in categories:
categories.append(line)
return categories
# returns the directory path of file_path
@staticmethod
def getDirPath(file_path):
return os.path.dirname(file_path)
# returns true or false regarding if line contains an image path (NoteImage, KeyImage, Hit0, Hit50, Hit100, Hit200, Hit300, Hit300g) # IniParser.isImageLine(line)
@staticmethod
def isImageLine(line):
return (IniParser.startsWith(line, "NoteImage") or
IniParser.startsWith(line, "KeyImage") or
IniParser.startsWith(line, "Stage") or
IniParser.startsWith(line, "WarningArrow") or
(IniParser.startsWith(line, "Lighting") and not IniParser.startsWith(line, "LightingLWidth")) or
(IniParser.startsWith(line, "Hit") and not IniParser.startsWith(line, "HitPosition")))
# returns a list of all local image paths from a skin.ini file
@staticmethod
def getImages(file_path):
images = []
for line in IniParser.getLines(file_path):
if IniParser.isImageLine(line) and IniParser.getValueFromLine(line) not in images:
images.append(IniParser.getValueFromLine(line))
return images
# returns a list of all complete image paths from a skin.ini file
@staticmethod
def getImagesPath(file_path):
images = []
for image in IniParser.getImages(file_path):
images.append(os.path.join(IniParser.getDirPath(
file_path), image.replace('/', os.sep)) + '.png')
return images
# returns a list of all complete image paths from a skin.ini file at a specific section
@staticmethod
def getSectionImages(file_path, section):
images = []
for line in section.splitlines():
if (IniParser.isImageLine(line)) and IniParser.getValueFromLine(line) not in images:
images.append((os.path.join(file_path, IniParser.getValueFromLine(
line)) + ".png").replace('/', os.sep))
return images
# returns a list of the corrected image paths for the files in the merge_folder
@staticmethod
def getNewSectionImages(section):
new_section = []
key_count = IniParser.getSectionKeycount(section)
for line in section.splitlines():
if (IniParser.isImageLine(line)) and IniParser.getValueFromLine(line):
new_section.append(
f"{IniParser.getKeyFromLine(line)}: merge_files{os.sep}{key_count}_key{os.sep}{os.path.basename(IniParser.getValueFromLine(line))}")
else:
new_section.append(line)
return "\n".join(new_section)
# returns the equivalent HD file path of an image
@staticmethod
def getHDImage(image_path):
base, extension = os.path.splitext(image_path)
return f"{base}@2x{extension}"
# EDIT FUNCTION: Edits a value of a keyvalue pair within a skin.ini file
@staticmethod
def editValue(file_path, value_name, new_value):
lines = IniParser.getLines(file_path)
for index, line in enumerate(lines):
if IniParser.startsWith(line, value_name + ":"):
lines[index] = f"{IniParser.getKeyFromLine(line)}: {new_value}\n"
with open(file_path, 'w', encoding="utf-8") as file:
file.writelines(lines)
# returns a list of all keycounts in a skin.ini file
@staticmethod
def getKeys(file_path):
keys = []
for line in IniParser.getLines(file_path):
if IniParser.startsWith(line, "keys:"):
key = int(IniParser.getValueFromLine(line))
if key not in keys:
keys.append(key)
keys.sort()
return keys
# seperates each [Mania] block into a dictionary with the key being the keycount that [Mania] block is associated with and the value being the block itself
@staticmethod
def dictKeySections(file_path):
key_sections = {}
lines = IniParser.getLines(file_path)
current_section = []
current_key = None
# add non [Mania] Section(s)
for line in lines:
line = line.strip()
if not IniParser.startsWith(line, "[mania]"):
current_section.append(line)
else:
break
key_sections[-1] = "\n".join(current_section)
current_section = []
for line in lines:
line = line.strip()
# if [Mania] line, append this line to current_section
if IniParser.startsWith(line, "[mania]"):
# if both current_section isnt empty and current_key isnt None, add the entire section to the dictionary and reset both variables
if (current_section and current_key is not None) and current_key not in key_sections:
key_sections[current_key] = "\n".join(current_section)
current_section = []
current_key = None
current_section.append(line)
# if line starts with "keys: " extract key value and set current_key
elif IniParser.startsWith(line, "keys:"):
current_key = int(IniParser.getValueFromLine(line))
current_section.append(line)
# if a section is already started add line to section
elif current_section:
current_section.append(line)
# add last pending section
if current_section and current_key is not None:
key_sections[current_key] = "\n".join(current_section)
return key_sections
# EDIT FUNCTION: replaces a key section within skin_file_path with section regarding key count num
@staticmethod
def replaceKeySection(file_path, section, num):
sections = IniParser.dictKeySections(file_path)
if num not in sections:
sections[num] = IniParser.getNewSectionImages(section[num])
sections = dict(sorted(sections.items()))
elif num in sections:
sections[num] = IniParser.getNewSectionImages(section[num])
new_file = ""
for i in sections:
new_file += sections[i] + "\n"
with open(file_path, 'w', encoding="utf-8") as file:
file.write(new_file)
# removes either @2x.png or .png from end
@staticmethod
def removeSuffix(path):
return path.removesuffix("@2x.png").removesuffix(".png")
# returns either @2x.png or .png that is at the end
def getSuffix(path):
if path.lower().endswith("@2x.png"):
return "@2x.png"
if path.lower().endswith(".png"):
return ".png"
@staticmethod
def animationExists(path):
suffix = IniParser.getSuffix(path)
return os.path.exists(f"{IniParser.removeSuffix(path)}-0{suffix}")
@staticmethod
def getAnimations(file_path):
suffix = IniParser.getSuffix(file_path)
animations = []
exists = True
num = 0
if IniParser.animationExists(file_path):
while exists:
animation = f"{IniParser.removeSuffix(file_path)}-{num}{suffix}"
if os.path.exists(animation):
animations.append(animation)
num += 1
else:
exists = False
return animations
return None
# adds a tag to the top of file_path "//Skins merged using github.com/Greenest-Guy/osu-mania-Skin-Merger\n"
@staticmethod
def addTag(file_path):
tag = "//Skins merged using github.com/Greenest-Guy/osu-mania-Skin-Merger\n"
with open(file_path, 'r', encoding="utf-8") as f:
original_content = f.read()
if tag not in original_content:
with open(file_path, 'w', encoding="utf-8") as file:
file.write(tag)
file.write(original_content)