ckparser 0.2__tar.gz → 0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ckparser-0.2/ckparser.egg-info → ckparser-0.3}/PKG-INFO +1 -1
- {ckparser-0.2 → ckparser-0.3/ckparser.egg-info}/PKG-INFO +1 -1
- {ckparser-0.2 → ckparser-0.3}/ckparser.py +59 -41
- {ckparser-0.2 → ckparser-0.3}/pyproject.toml +1 -1
- {ckparser-0.2 → ckparser-0.3}/LICENSE +0 -0
- {ckparser-0.2 → ckparser-0.3}/MANIFEST.in +0 -0
- {ckparser-0.2 → ckparser-0.3}/README.md +0 -0
- {ckparser-0.2 → ckparser-0.3}/ckparser.egg-info/SOURCES.txt +0 -0
- {ckparser-0.2 → ckparser-0.3}/ckparser.egg-info/dependency_links.txt +0 -0
- {ckparser-0.2 → ckparser-0.3}/ckparser.egg-info/entry_points.txt +0 -0
- {ckparser-0.2 → ckparser-0.3}/ckparser.egg-info/requires.txt +0 -0
- {ckparser-0.2 → ckparser-0.3}/ckparser.egg-info/top_level.txt +0 -0
- {ckparser-0.2 → ckparser-0.3}/setup.cfg +0 -0
|
@@ -41,7 +41,7 @@ import re
|
|
|
41
41
|
import time
|
|
42
42
|
|
|
43
43
|
# Script version
|
|
44
|
-
__version__ = "0.
|
|
44
|
+
__version__ = "0.3"
|
|
45
45
|
|
|
46
46
|
# Logger (because logging is awesome)
|
|
47
47
|
logger = logging.getLogger(__name__)
|
|
@@ -64,16 +64,26 @@ class JominiJSONEncoder(json.JSONEncoder):
|
|
|
64
64
|
|
|
65
65
|
def jomini_object_hook(obj):
|
|
66
66
|
# JSON specific decoder for dates
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if isinstance(obj, dict):
|
|
68
|
+
for key, value in obj.items():
|
|
69
|
+
if isinstance(value, str) and regex_date.fullmatch(value):
|
|
70
|
+
obj[key] = convert_date(value) or value
|
|
71
|
+
elif isinstance(value, list):
|
|
72
|
+
obj[key] = jomini_object_hook(value)
|
|
73
|
+
elif isinstance(obj, list):
|
|
74
|
+
for index in range(len(obj)):
|
|
75
|
+
item = obj[index]
|
|
76
|
+
if isinstance(item, str) and regex_date.fullmatch(item):
|
|
77
|
+
obj[index] = convert_date(item) or item
|
|
78
|
+
elif isinstance(item, list):
|
|
79
|
+
obj[index] = jomini_object_hook(item)
|
|
70
80
|
return obj
|
|
71
81
|
|
|
72
82
|
|
|
73
83
|
json_load = functools.partial(json.load, object_hook=jomini_object_hook)
|
|
74
84
|
json_loads = functools.partial(json.loads, object_hook=jomini_object_hook)
|
|
75
|
-
json_dump = functools.partial(json.dump, cls=JominiJSONEncoder, indent=4
|
|
76
|
-
json_dumps = functools.partial(json.dumps, cls=JominiJSONEncoder, indent=4
|
|
85
|
+
json_dump = functools.partial(json.dump, cls=JominiJSONEncoder, indent=4)
|
|
86
|
+
json_dumps = functools.partial(json.dumps, cls=JominiJSONEncoder, indent=4)
|
|
77
87
|
|
|
78
88
|
# Boolean transformation
|
|
79
89
|
booleans = {"yes": True, "no": False}
|
|
@@ -92,7 +102,7 @@ regex_string_multiline = re.compile(r"\"[^\"]*\"", re.MULTILINE)
|
|
|
92
102
|
# Regex for quoted strings inside quoted strings
|
|
93
103
|
regex_inner_string = re.compile(r"\|(?P<index>\d+)\|")
|
|
94
104
|
# Regex to remove comments in files
|
|
95
|
-
regex_comment = re.compile(r"(?P<space>\s*)(?P<comment>#.*)
|
|
105
|
+
regex_comment = re.compile(r"(?P<space>\s*)(?P<comment>#.*)")
|
|
96
106
|
# Regex to fix blocks with no equal sign
|
|
97
107
|
regex_block = re.compile(r"^([^\s\{\=]+)\s*\{\s*$", re.MULTILINE)
|
|
98
108
|
# Regex to remove "list" prefix
|
|
@@ -101,8 +111,8 @@ regex_list = re.compile(r"\s*=\s*list\s+([\{\"\|])", re.MULTILINE)
|
|
|
101
111
|
regex_color = re.compile(r"=\s*(?P<type>\w+)\s*{")
|
|
102
112
|
# Regex to parse items with format key=value
|
|
103
113
|
regex_inline = re.compile(r"([^\s\"]+\s*[?!<=>]+\s*(([^@\"]\[?[^\s]+\]?)|(\"[^\"]+\")|(@\[[^\]]+\]))|(@\w+))")
|
|
104
|
-
# Regex to parse blocks with bracket below the key
|
|
105
|
-
regex_values = re.compile(r"(([?!<=>]+)\s
|
|
114
|
+
# Regex to parse blocks with bracket below the key/operator
|
|
115
|
+
regex_values = re.compile(r"(\s*([?!<=>]+)\s+\{)|(\s+\s*([?!<=>])+\s*\{)")
|
|
106
116
|
# Regex to parse lines with format key=value
|
|
107
117
|
regex_line = re.compile(r"\"?(?P<key>[^\s\"]+)\"?\s*(?P<operator>[?!<=>]+)\s*(list\s*)?(?P<value>.*)")
|
|
108
118
|
# Regex to parse independent items in a list
|
|
@@ -115,6 +125,8 @@ regex_empty = re.compile(r"(\n\s*\n)+", re.MULTILINE)
|
|
|
115
125
|
regex_locale = re.compile(r"^\s*(?P<key>[^\:#]+)\:(\d+)?\s\"(?P<value>.+)\".*$")
|
|
116
126
|
# Regex for keywords
|
|
117
127
|
regex_keyword = re.compile(r"(" + "|".join(map(re.escape, sorted(keywords, key=len, reverse=True))) + r") ")
|
|
128
|
+
# Regex for fixing line count when bracket is below the key/operator
|
|
129
|
+
regex_count = re.compile(r"([?!<=>]+)\s*§\n+\s*\{")
|
|
118
130
|
# Regex for string indexes
|
|
119
131
|
regex_index = re.compile(r"\|(\d+)\|")
|
|
120
132
|
# Regex for variables
|
|
@@ -172,7 +184,7 @@ def read_file(path, encoding="utf_8_sig"):
|
|
|
172
184
|
encoding = result["encoding"]
|
|
173
185
|
logger.debug(f"Detected encoding: {result['encoding']} ({result['confidence']:0.0%})")
|
|
174
186
|
del raw_data
|
|
175
|
-
with open(path, encoding=encoding) as file:
|
|
187
|
+
with open(path, "r", encoding=encoding) as file:
|
|
176
188
|
return file.read()
|
|
177
189
|
|
|
178
190
|
|
|
@@ -189,19 +201,19 @@ def parse_text(text, return_text_on_error=False, comments=False, dates=False, fi
|
|
|
189
201
|
"""
|
|
190
202
|
|
|
191
203
|
def replace(match):
|
|
192
|
-
nonlocal strings,
|
|
193
|
-
|
|
194
|
-
strings[str(
|
|
195
|
-
return f"|{
|
|
204
|
+
nonlocal strings, strings_index
|
|
205
|
+
strings_index = len(strings)
|
|
206
|
+
strings[str(strings_index)] = match.group(0).replace("\n", "\\n")
|
|
207
|
+
return f"|{strings_index}|"
|
|
196
208
|
|
|
197
209
|
def replace_comment(match):
|
|
198
|
-
nonlocal strings,
|
|
199
|
-
|
|
210
|
+
nonlocal strings, strings_index
|
|
211
|
+
strings_index = len(strings)
|
|
200
212
|
value, space = match.group("comment").replace('"', "'").strip(), match.group("space")
|
|
201
|
-
strings[str(
|
|
213
|
+
strings[str(strings_index)] = f'"{value}"'
|
|
202
214
|
if not value.strip():
|
|
203
215
|
return ""
|
|
204
|
-
return f"\n{space}#{
|
|
216
|
+
return f"\n{space}#{strings_index}=|{strings_index}|"
|
|
205
217
|
|
|
206
218
|
def set_variable(key, value, is_global=is_global):
|
|
207
219
|
global global_variables
|
|
@@ -213,29 +225,35 @@ def parse_text(text, return_text_on_error=False, comments=False, dates=False, fi
|
|
|
213
225
|
|
|
214
226
|
root = {}
|
|
215
227
|
nodes = [("", root)]
|
|
216
|
-
strings,
|
|
228
|
+
strings, strings_index = {}, 0
|
|
217
229
|
variables = global_variables.copy()
|
|
218
230
|
# Cleaning document
|
|
231
|
+
text = text.replace("\n", "\n§\n")
|
|
219
232
|
text = regex_string.sub(replace, text)
|
|
220
233
|
if comments:
|
|
221
234
|
text = regex_comment.sub(replace_comment, text)
|
|
222
235
|
else:
|
|
223
236
|
text = regex_comment.sub("", text)
|
|
237
|
+
text = text.replace("{", "\n{\n").replace("}", "\n}\n")
|
|
224
238
|
text = regex_string_multiline.sub(replace, text)
|
|
225
239
|
text = regex_list.sub(r"|list=\g<1>", text)
|
|
226
|
-
text = regex_block.sub(r"\g<1>={", text)
|
|
227
|
-
text = text.replace("{", "\n{\n").replace("}", "\n}\n")
|
|
228
240
|
text = regex_color.sub(r"={\n\g<1>", text)
|
|
229
241
|
text = regex_inline.sub(r"\g<1>\n", text)
|
|
230
|
-
text = regex_values.sub(r"\g<2>\g<4>", text)
|
|
242
|
+
text = regex_values.sub(r"\g<2>\g<4>{", text)
|
|
231
243
|
text = regex_empty.sub(r"\n", text)
|
|
232
244
|
text = regex_keyword.sub(r"\1|", text)
|
|
245
|
+
text = regex_count.sub(r"\g<1>{\n§", text)
|
|
233
246
|
text = regex_index.sub(lambda match: strings[match.group(1)], text)
|
|
234
247
|
|
|
235
248
|
# Parsing document line by line
|
|
236
|
-
|
|
249
|
+
line_number = 1
|
|
250
|
+
for line_text in text.splitlines():
|
|
237
251
|
try:
|
|
238
252
|
line_text = line_text.strip()
|
|
253
|
+
# Line number
|
|
254
|
+
if count := line_text.count("§"):
|
|
255
|
+
line_number += count
|
|
256
|
+
line_text = line_text.rstrip("§")
|
|
239
257
|
# Nothing to do if line is empty
|
|
240
258
|
if not line_text:
|
|
241
259
|
continue
|
|
@@ -277,7 +295,7 @@ def parse_text(text, return_text_on_error=False, comments=False, dates=False, fi
|
|
|
277
295
|
value = val
|
|
278
296
|
elif value:
|
|
279
297
|
# Try to convert value to Python value
|
|
280
|
-
if dates and regex_date.
|
|
298
|
+
if dates and regex_date.fullmatch(value):
|
|
281
299
|
value = convert_date(value) or value
|
|
282
300
|
else:
|
|
283
301
|
try:
|
|
@@ -360,7 +378,7 @@ def parse_text(text, return_text_on_error=False, comments=False, dates=False, fi
|
|
|
360
378
|
node[key] = {"@type": "variable", "@value": value, "@result": result}
|
|
361
379
|
if result is not None and (key.startswith("@") or len(nodes) == 1):
|
|
362
380
|
set_variable(key, result)
|
|
363
|
-
elif result := variables.get(value):
|
|
381
|
+
elif (result := variables.get(value)) is not None:
|
|
364
382
|
if not isinstance(result, str):
|
|
365
383
|
node[key] = {"@type": "variable", "@value": value, "@result": result}
|
|
366
384
|
if result is not None and (key.startswith("@") or len(nodes) == 1):
|
|
@@ -455,7 +473,7 @@ def parse_text(text, return_text_on_error=False, comments=False, dates=False, fi
|
|
|
455
473
|
logger.warning(f"Filename: {filename}")
|
|
456
474
|
logger.warning(f'Value for "{value}" cannot be found (line: {line_number})')
|
|
457
475
|
node.append({"@type": "variable", "@value": item, "@result": result})
|
|
458
|
-
elif dates and regex_date.
|
|
476
|
+
elif dates and regex_date.fullmatch(item):
|
|
459
477
|
item = convert_date(item) or item
|
|
460
478
|
node.append(item)
|
|
461
479
|
else:
|
|
@@ -501,32 +519,32 @@ def parse_file(
|
|
|
501
519
|
start_time = time.monotonic()
|
|
502
520
|
if base_dir:
|
|
503
521
|
base_dir = os.sep.join(str(base_dir).rstrip(os.sep).split(os.sep)[:-1]) + os.sep
|
|
504
|
-
base_dir = os.path.dirname(path.replace(base_dir
|
|
522
|
+
base_dir = os.path.dirname(path.replace(base_dir, ""))
|
|
505
523
|
base_dir = base_dir or "."
|
|
506
524
|
text = read_file(path, encoding)
|
|
507
525
|
if not text or not text.strip():
|
|
508
526
|
return None
|
|
509
527
|
for pattern, replacement in patch or []:
|
|
510
528
|
text = re.sub(pattern, replacement, text)
|
|
511
|
-
filename = os.path.join(base_dir, os.path.basename(path))
|
|
529
|
+
filename = os.path.join(base_dir, os.path.basename(path))
|
|
512
530
|
logger.debug(f"Parsing {filename}")
|
|
513
531
|
data = parse_text(
|
|
514
532
|
text, return_text_on_error=True, comments=comments, dates=dates, filename=filename, is_global=is_global
|
|
515
533
|
)
|
|
516
534
|
if save:
|
|
517
535
|
filename, _ = os.path.splitext(os.path.basename(path))
|
|
518
|
-
directory = os.path.join(output_dir or "output", *base_dir.split(
|
|
536
|
+
directory = os.path.join(output_dir or "output", *base_dir.split(os.sep))
|
|
519
537
|
os.makedirs(directory, exist_ok=True)
|
|
520
538
|
if not isinstance(data, dict):
|
|
521
539
|
filename = os.path.join(directory, filename + ".error")
|
|
522
540
|
try:
|
|
523
|
-
with open(filename, "w") as file:
|
|
541
|
+
with open(filename, "w", encoding=encoding) as file:
|
|
524
542
|
file.write(data)
|
|
525
543
|
except UnicodeEncodeError as error:
|
|
526
544
|
logger.error(f"Unable to write file {filename}: {error}")
|
|
527
545
|
else:
|
|
528
546
|
filename = os.path.join(directory, filename + ".json")
|
|
529
|
-
with open(filename, "w") as file:
|
|
547
|
+
with open(filename, "w", encoding="utf-8") as file:
|
|
530
548
|
json_dump(data, file)
|
|
531
549
|
total_time = time.monotonic() - start_time
|
|
532
550
|
logger.debug(f"Elapsed time: {total_time:0.3f}s!")
|
|
@@ -579,7 +597,7 @@ def parse_all_files(
|
|
|
579
597
|
for filename in all_files:
|
|
580
598
|
if not filename.lower().endswith(".txt"):
|
|
581
599
|
continue
|
|
582
|
-
filepath = os.path.join(current_path, filename)
|
|
600
|
+
filepath = os.path.join(current_path, filename)
|
|
583
601
|
data = parse_file(
|
|
584
602
|
filepath,
|
|
585
603
|
output_dir=output_dir,
|
|
@@ -593,7 +611,7 @@ def parse_all_files(
|
|
|
593
611
|
if isinstance(data, str):
|
|
594
612
|
errors.append(filepath)
|
|
595
613
|
continue
|
|
596
|
-
filepath = filepath.replace(str(path), "").lstrip(
|
|
614
|
+
filepath = filepath.replace(str(path), "").lstrip(os.sep)
|
|
597
615
|
success[filepath] = data if keep_data else True
|
|
598
616
|
total_time = time.monotonic() - start_time
|
|
599
617
|
logger.info(f"{len(success)} parsed file(s) and {len(errors)} errors in {total_time:0.3f}s!")
|
|
@@ -602,14 +620,14 @@ def parse_all_files(
|
|
|
602
620
|
return success
|
|
603
621
|
|
|
604
622
|
|
|
605
|
-
def parse_all_locales(path, encoding="utf_8_sig", language="english", save=False,
|
|
623
|
+
def parse_all_locales(path, encoding="utf_8_sig", language="english", save=False, output="_locales.json"):
|
|
606
624
|
"""
|
|
607
625
|
Parse all locales strings
|
|
608
626
|
:param path: Path where to find locale files
|
|
609
627
|
:param encoding: Encoding for reading files
|
|
610
628
|
:param language: Target language
|
|
611
629
|
:param save: (default false) save locales in file
|
|
612
|
-
:param
|
|
630
|
+
:param output: locales output file location
|
|
613
631
|
:return: Locales in dictionary
|
|
614
632
|
"""
|
|
615
633
|
locales = {}
|
|
@@ -637,7 +655,7 @@ def parse_all_locales(path, encoding="utf_8_sig", language="english", save=False
|
|
|
637
655
|
key, _, value = match.groups()
|
|
638
656
|
locales[key] = value
|
|
639
657
|
if save:
|
|
640
|
-
with open(
|
|
658
|
+
with open(output, "w", encoding="utf-8") as file:
|
|
641
659
|
json_dump(locales, file, sort_keys=True)
|
|
642
660
|
return locales
|
|
643
661
|
|
|
@@ -812,8 +830,8 @@ def revert_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save
|
|
|
812
830
|
base_dir = os.sep.join(str(base_dir).rstrip(os.sep).split(os.sep)[:-1]) + os.sep
|
|
813
831
|
base_dir = os.path.dirname(path.replace(base_dir, ""))
|
|
814
832
|
base_dir = base_dir or "."
|
|
815
|
-
with open(path) as file:
|
|
816
|
-
data =
|
|
833
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
834
|
+
data = json.load(file)
|
|
817
835
|
filename = os.path.join(str(base_dir), os.path.basename(path))
|
|
818
836
|
logger.debug(f"Reverting {filename}")
|
|
819
837
|
text = revert(data)
|
|
@@ -836,8 +854,8 @@ def load_variables(filepath="_variables.json"):
|
|
|
836
854
|
global global_variables
|
|
837
855
|
if not os.path.exists(filepath):
|
|
838
856
|
return
|
|
839
|
-
with open(filepath) as file:
|
|
840
|
-
global_variables =
|
|
857
|
+
with open(filepath, "r", encoding="utf-8") as file:
|
|
858
|
+
global_variables = json.load(file)
|
|
841
859
|
|
|
842
860
|
|
|
843
861
|
def save_variables(filepath="_variables.json"):
|
|
@@ -846,7 +864,7 @@ def save_variables(filepath="_variables.json"):
|
|
|
846
864
|
"""
|
|
847
865
|
if not global_variables:
|
|
848
866
|
return
|
|
849
|
-
with open(filepath, "w") as file:
|
|
867
|
+
with open(filepath, "w", encoding="utf-8") as file:
|
|
850
868
|
json_dump(global_variables, file, sort_keys=True)
|
|
851
869
|
|
|
852
870
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|