Skip to content

Commit 0c9b797

Browse files
committed
Merge branch 'main' of https://github.com/garcia/simfile into main
2 parents 9542644 + ed13b5f commit 0c9b797

File tree

5 files changed

+151
-17
lines changed

5 files changed

+151
-17
lines changed

docs/source/examples.rst

+23-12
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ sort_by_difficulty.py
253253
so that they are sorted by difficulty in StepMania.
254254
255255
This script finds the hardest chart of a given stepstype (dance-single by default)
256-
and puts its meter (difficulty number) between brackets at the start of the title.
256+
and puts its meter (difficulty number) between brackets at the start of the title
257+
and titletranslit.
257258
258259
Usage examples:
259260
@@ -321,7 +322,7 @@ sort_by_difficulty.py
321322
322323
def prefix_title_with_meter(simfile_path: str, args: SortByDifficultyArgs):
323324
"""
324-
Add (or remove) a numeric prefix to the simfile's title.
325+
Add (or remove) a numeric prefix to the simfile's title and titletranslit.
325326
326327
This saves the updated simfile to its original location
327328
and writes a backup copy with a ~ appended to the filename.
@@ -336,18 +337,27 @@ sort_by_difficulty.py
336337
# It's very unlikely for the title property to be blank or missing.
337338
# This is mostly to satisfy type-checkers.
338339
current_title = sf.title or ""
340+
current_titletranslit = sf.titletranslit or ""
339341
340342
if args.remove:
341-
# Look for a number in brackets at the start of the title
342-
if current_title.startswith("["):
343-
open_bracket_index = current_title.find("[")
344-
close_bracket_index = current_title.find("]")
345-
bracketed_text = current_title[
346-
open_bracket_index + 1 : close_bracket_index
347-
]
348-
if bracketed_text.isnumeric():
349-
# Remove the bracketed number from the title
350-
sf.title = current_title[close_bracket_index + 1 :].lstrip(" ")
343+
def remove_starting_brackets(current_text: str) -> str:
344+
"""
345+
If current_text has a bracketed number at the start of the text, remove it and return it
346+
Otherwise, return current_text unchanged.
347+
"""
348+
# Look for a number in brackets at the start of the text
349+
if current_text.startswith("["):
350+
open_bracket_index = current_text.find("[")
351+
close_bracket_index = current_text.find("]")
352+
bracketed_text = current_text[
353+
open_bracket_index + 1 : close_bracket_index
354+
]
355+
if bracketed_text.isnumeric():
356+
# Remove the bracketed number from the text
357+
return current_title[close_bracket_index + 1 :].lstrip(" ")
358+
return current_title
359+
sf.title = remove_starting_brackets(sf.title)
360+
sf.titletranslit = remove_starting_brackets(sf.titletranslit)
351361
else:
352362
# Find the hardest chart (numerically) within a stepstype
353363
# and use it to prefix the title
@@ -365,6 +375,7 @@ sort_by_difficulty.py
365375
# Put the meter at the start of the title,
366376
# filling in leading zeros per arguments
367377
sf.title = f"[{meter.zfill(args.digits)}] {current_title}"
378+
sf.titletranslit = f"[{meter.zfill(args.digits)}] {current_titletranslit}"
368379
369380
370381
def main(argv):

simfile/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .types import Simfile
2323

2424

25-
__version__ = "2.1.0"
25+
__version__ = "2.1.1"
2626
__all__ = [
2727
"load",
2828
"loads",

simfile/ssc.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,28 @@ def _parse(self, parser: MSD_ITERATOR) -> None:
9999
raise ValueError("expected NOTEDATA property first")
100100

101101
for param in iterator:
102-
self[param.key] = param.value
102+
if param.key in BaseSimfile.MULTI_VALUE_PROPERTIES:
103+
self[param.key] = ":".join(param.components[1:])
104+
else:
105+
self[param.key] = param.value
103106
if param.value is self.notes:
104107
break
105108

106109
def serialize(self, file):
107110
file.write(f"{MSDParameter(('NOTEDATA', ''))}\n")
108111
notes_key = "NOTES"
112+
109113
for (key, value) in self.items():
110-
# notes must always be the last property in a chart
114+
# Either NOTES or NOTES2 must be the last chart property
111115
if value is self.notes:
112116
notes_key = key
113117
continue
114-
param = MSDParameter((key, value))
118+
if key in BaseSimfile.MULTI_VALUE_PROPERTIES:
119+
param = MSDParameter((key, *value.split(":")))
120+
else:
121+
param = MSDParameter((key, value))
115122
file.write(f"{param}\n")
123+
116124
notes_param = MSDParameter((notes_key, self[notes_key]))
117125
file.write(f"{notes_param}\n\n")
118126

@@ -208,7 +216,7 @@ def _parse(self, parser: MSD_ITERATOR):
208216
partial_chart: Optional[SSCChart] = None
209217
for param in parser:
210218
key = param.key.upper()
211-
if key not in BaseSimfile.MULTI_VALUE_PROPERTIES:
219+
if key in BaseSimfile.MULTI_VALUE_PROPERTIES:
212220
value: Optional[str] = ":".join(param.components[1:])
213221
else:
214222
value = param.value

simfile/tests/test_sm.py

+30
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,20 @@ def test_init_handles_animations_property(self):
159159
self.assertEqual(with_bgchanges.bgchanges, with_animations.bgchanges)
160160
self.assertNotIn("BGCHANGES", with_animations)
161161
self.assertIn("ANIMATIONS", with_animations)
162+
163+
def test_init_handles_multi_value_properties(self):
164+
with_multi_value_properties = SMSimfile(string='''
165+
#TITLE:Colons should be preserved below: but not here;
166+
#DISPLAYBPM:60:240;
167+
#ATTACKS:TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse;
168+
''')
169+
self.assertEqual('Colons should be preserved below', with_multi_value_properties.title)
170+
self.assertEqual('60:240', with_multi_value_properties.displaybpm)
171+
self.assertEqual(
172+
'TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse',
173+
with_multi_value_properties.attacks,
174+
)
175+
162176

163177
def test_repr(self):
164178
unit = SMSimfile(string=testing_simfile())
@@ -198,3 +212,19 @@ def test_charts(self):
198212

199213
unit.charts = SMCharts()
200214
self.assertEqual(0, len(unit.charts))
215+
216+
def test_serialize_handles_multi_value_properties(self):
217+
expected = SMSimfile(string='''
218+
#TITLE:Colons should be preserved below;
219+
#DISPLAYBPM:60:240;
220+
#ATTACKS:TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse;
221+
''')
222+
223+
# None of the colons should be escaped
224+
serialized = str(expected)
225+
self.assertNotIn('\\', serialized)
226+
227+
deserialized = SMSimfile(string=serialized)
228+
self.assertEqual(expected.title, deserialized.title)
229+
self.assertEqual(expected.displaybpm, deserialized.displaybpm)
230+
self.assertEqual(expected.attacks, deserialized.attacks)

simfile/tests/test_ssc.py

+85
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,29 @@ def test_init_and_properties(self):
5959
self.assertEqual("0.793,1.205,0.500,0.298,0.961", unit.radarvalues)
6060
self.assertEqual("\n\n0000\n0000\n0000\n0000\n", unit.notes)
6161

62+
def test_init_handles_multi_value_properties(self):
63+
with_multi_value_properties = SSCChart.from_str(
64+
"""
65+
#NOTEDATA:;
66+
#CHARTNAME:Colons should be preserved below: but not here;
67+
#DISPLAYBPM:60:240;
68+
#ATTACKS:TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse;
69+
#NOTES:
70+
0000
71+
0000
72+
0000
73+
0000
74+
;"""
75+
)
76+
self.assertEqual(
77+
"Colons should be preserved below", with_multi_value_properties.chartname
78+
)
79+
self.assertEqual("60:240", with_multi_value_properties.displaybpm)
80+
self.assertEqual(
81+
"TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse",
82+
with_multi_value_properties.attacks,
83+
)
84+
6285
def test_serialize(self):
6386
unit = SSCChart.from_str(testing_chart())
6487
expected = (
@@ -81,6 +104,31 @@ def test_serialize(self):
81104
"\n"
82105
)
83106

107+
def test_serialize_handles_multi_value_properties(self):
108+
expected = SSCChart.from_str(
109+
"""
110+
#NOTEDATA:;
111+
#CHARTNAME:Colons in values below should be preserved;
112+
#DISPLAYBPM:60:240;
113+
#ATTACKS:TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse;
114+
#NOTES:
115+
0000
116+
0000
117+
0000
118+
0000
119+
;"""
120+
)
121+
serialized = SSCChart.from_str(str(expected))
122+
123+
# None of the colons should be escaped
124+
serialized = str(expected)
125+
self.assertNotIn("\\", serialized)
126+
127+
deserialized = SSCChart.from_str(str(serialized))
128+
self.assertEqual(expected.chartname, deserialized.chartname)
129+
self.assertEqual(expected.displaybpm, deserialized.displaybpm)
130+
self.assertEqual(expected.attacks, deserialized.attacks)
131+
84132
def test_serialize_with_escapes(self):
85133
unit = SSCChart.from_str(testing_chart())
86134
unit.chartname = "A:B;C//D\\E"
@@ -194,6 +242,43 @@ def test_init_handles_animations_property(self):
194242
self.assertNotIn("BGCHANGES", with_animations)
195243
self.assertIn("ANIMATIONS", with_animations)
196244

245+
def test_init_handles_multi_value_properties(self):
246+
with_multi_value_properties = SSCSimfile(
247+
string="""
248+
#VERSION:0.83;
249+
#TITLE:Colons should be preserved below: but not here;
250+
#DISPLAYBPM:60:240;
251+
#ATTACKS:TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse;
252+
"""
253+
)
254+
self.assertEqual(
255+
"Colons should be preserved below", with_multi_value_properties.title
256+
)
257+
self.assertEqual("60:240", with_multi_value_properties.displaybpm)
258+
self.assertEqual(
259+
"TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse",
260+
with_multi_value_properties.attacks,
261+
)
262+
263+
def test_serialize_handles_multi_value_properties(self):
264+
expected = SSCSimfile(
265+
string="""
266+
#VERSION:0.83;
267+
#TITLE:Colons should be preserved below;
268+
#DISPLAYBPM:60:240;
269+
#ATTACKS:TIME=1.000:LEN=0.500:MODS=*5 -2.5 reverse;
270+
"""
271+
)
272+
273+
# None of the colons should be escaped
274+
serialized = str(expected)
275+
self.assertNotIn("\\", serialized)
276+
277+
deserialized = SSCSimfile(string=serialized)
278+
self.assertEqual(expected.title, deserialized.title)
279+
self.assertEqual(expected.displaybpm, deserialized.displaybpm)
280+
self.assertEqual(expected.attacks, deserialized.attacks)
281+
197282
def test_repr(self):
198283
unit = SSCSimfile(string=testing_simfile())
199284

0 commit comments

Comments
 (0)