ckparser 0.3.2__tar.gz → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ckparser
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Parse Paradox Jomini data files to Python/JSON and revert them back experimentally.
5
5
  Author: Marc Debureaux (debnet)
6
6
  License-Expression: MIT
@@ -284,6 +284,29 @@ from ckparser import walk
284
284
  for value, path in walk(data):
285
285
  print(path, value)
286
286
  ```
287
+
288
+ ### `objectify`
289
+
290
+ Transform a dictionary datastructure into an object-like dictionary which allows a better inspection.
291
+
292
+ ```python
293
+ from ckparser import objectify
294
+
295
+ data = {
296
+ "name": "Louis",
297
+ "traits": [
298
+ "brave",
299
+ "diligent"
300
+ ],
301
+ "father": {
302
+ "name": "Charles"
303
+ }
304
+ }
305
+
306
+ obj = objectify(data)
307
+ print(obj.traits[1])
308
+ print(obj.father.name)
309
+ ```
287
310
 
288
311
  ### `parse_all_locales`
289
312
 
@@ -518,9 +541,9 @@ Paradox scripting languages include many practical exceptions, formatting varian
518
541
 
519
542
  ### Parsing
520
543
 
521
- * `parse_text(text, return_text_on_error=False, comments=False, dates=False, filename=None, is_global=False)`
522
- * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=False, dates=False, is_global=False, patch=None)`
523
- * `parse_all_files(path, output_dir=None, encoding="utf_8_sig", keep_data=False, save=False, comments=False, dates=False, variables_first=True)`
544
+ * `parse_text(text, return_text_on_error=False, comments=False, dates=False, as_object=False, filename=None, is_global=False)`
545
+ * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=False, dates=False, as_object=False, is_global=False, patch=None)`
546
+ * `parse_all_files(path, output_dir=None, encoding="utf_8_sig", keep_data=False, save=False, comments=False, dates=False, as_object=False, variables_first=True)`
524
547
 
525
548
  ### Reversion
526
549
 
@@ -533,6 +556,7 @@ Paradox scripting languages include many practical exceptions, formatting varian
533
556
  * `convert_date(date, key=None)`
534
557
  * `read_file(path, encoding="utf_8_sig")`
535
558
  * `walk(obj, *from_keys)`
559
+ * `objectify(item, name="Object", default=None)`
536
560
  * `parse_all_locales(path, encoding="utf_8_sig", language="english", save=False)`
537
561
  * `load_variables(filepath="_variables.json")`
538
562
  * `save_variables(filepath="_variables.json")`
@@ -253,6 +253,29 @@ from ckparser import walk
253
253
  for value, path in walk(data):
254
254
  print(path, value)
255
255
  ```
256
+
257
+ ### `objectify`
258
+
259
+ Transform a dictionary datastructure into an object-like dictionary which allows a better inspection.
260
+
261
+ ```python
262
+ from ckparser import objectify
263
+
264
+ data = {
265
+ "name": "Louis",
266
+ "traits": [
267
+ "brave",
268
+ "diligent"
269
+ ],
270
+ "father": {
271
+ "name": "Charles"
272
+ }
273
+ }
274
+
275
+ obj = objectify(data)
276
+ print(obj.traits[1])
277
+ print(obj.father.name)
278
+ ```
256
279
 
257
280
  ### `parse_all_locales`
258
281
 
@@ -487,9 +510,9 @@ Paradox scripting languages include many practical exceptions, formatting varian
487
510
 
488
511
  ### Parsing
489
512
 
490
- * `parse_text(text, return_text_on_error=False, comments=False, dates=False, filename=None, is_global=False)`
491
- * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=False, dates=False, is_global=False, patch=None)`
492
- * `parse_all_files(path, output_dir=None, encoding="utf_8_sig", keep_data=False, save=False, comments=False, dates=False, variables_first=True)`
513
+ * `parse_text(text, return_text_on_error=False, comments=False, dates=False, as_object=False, filename=None, is_global=False)`
514
+ * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=False, dates=False, as_object=False, is_global=False, patch=None)`
515
+ * `parse_all_files(path, output_dir=None, encoding="utf_8_sig", keep_data=False, save=False, comments=False, dates=False, as_object=False, variables_first=True)`
493
516
 
494
517
  ### Reversion
495
518
 
@@ -502,6 +525,7 @@ Paradox scripting languages include many practical exceptions, formatting varian
502
525
  * `convert_date(date, key=None)`
503
526
  * `read_file(path, encoding="utf_8_sig")`
504
527
  * `walk(obj, *from_keys)`
528
+ * `objectify(item, name="Object", default=None)`
505
529
  * `parse_all_locales(path, encoding="utf_8_sig", language="english", save=False)`
506
530
  * `load_variables(filepath="_variables.json")`
507
531
  * `save_variables(filepath="_variables.json")`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ckparser
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Parse Paradox Jomini data files to Python/JSON and revert them back experimentally.
5
5
  Author: Marc Debureaux (debnet)
6
6
  License-Expression: MIT
@@ -284,6 +284,29 @@ from ckparser import walk
284
284
  for value, path in walk(data):
285
285
  print(path, value)
286
286
  ```
287
+
288
+ ### `objectify`
289
+
290
+ Transform a dictionary datastructure into an object-like dictionary which allows a better inspection.
291
+
292
+ ```python
293
+ from ckparser import objectify
294
+
295
+ data = {
296
+ "name": "Louis",
297
+ "traits": [
298
+ "brave",
299
+ "diligent"
300
+ ],
301
+ "father": {
302
+ "name": "Charles"
303
+ }
304
+ }
305
+
306
+ obj = objectify(data)
307
+ print(obj.traits[1])
308
+ print(obj.father.name)
309
+ ```
287
310
 
288
311
  ### `parse_all_locales`
289
312
 
@@ -518,9 +541,9 @@ Paradox scripting languages include many practical exceptions, formatting varian
518
541
 
519
542
  ### Parsing
520
543
 
521
- * `parse_text(text, return_text_on_error=False, comments=False, dates=False, filename=None, is_global=False)`
522
- * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=False, dates=False, is_global=False, patch=None)`
523
- * `parse_all_files(path, output_dir=None, encoding="utf_8_sig", keep_data=False, save=False, comments=False, dates=False, variables_first=True)`
544
+ * `parse_text(text, return_text_on_error=False, comments=False, dates=False, as_object=False, filename=None, is_global=False)`
545
+ * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=False, dates=False, as_object=False, is_global=False, patch=None)`
546
+ * `parse_all_files(path, output_dir=None, encoding="utf_8_sig", keep_data=False, save=False, comments=False, dates=False, as_object=False, variables_first=True)`
524
547
 
525
548
  ### Reversion
526
549
 
@@ -533,6 +556,7 @@ Paradox scripting languages include many practical exceptions, formatting varian
533
556
  * `convert_date(date, key=None)`
534
557
  * `read_file(path, encoding="utf_8_sig")`
535
558
  * `walk(obj, *from_keys)`
559
+ * `objectify(item, name="Object", default=None)`
536
560
  * `parse_all_locales(path, encoding="utf_8_sig", language="english", save=False)`
537
561
  * `load_variables(filepath="_variables.json")`
538
562
  * `save_variables(filepath="_variables.json")`
@@ -41,7 +41,7 @@ import re
41
41
  import time
42
42
 
43
43
  # Script version
44
- __version__ = "0.3.2"
44
+ __version__ = "0.3.3"
45
45
 
46
46
  # Logger (because logging is awesome)
47
47
  logger = logging.getLogger(__name__)
@@ -120,7 +120,7 @@ regex_line = re.compile(r"\"?(?P<key>[^\s\"]+)\"?\s*(?P<operator>[?!<=>]+)\s*(li
120
120
  # Regex to parse independent items in a list
121
121
  regex_item = re.compile(r"(\"[^\"]+\"|[\d\.]+|[^\s]+)")
122
122
  # Regex to parse dates
123
- regex_date = re.compile(r"^(?:\d{1,4})\.(?:[1-9]|1[0-2])\.(?:[1-9]|[12]\d|3[01])$")
123
+ regex_date = re.compile(r"^(?P<year>\d{1,4})\.(?P<month>[1-9]|1[0-2])\.(?P<day>[1-9]|[12]\d|3[01])$")
124
124
  # Regex to remove empty lines
125
125
  regex_empty = re.compile(r"(\n\s*\n)+", re.MULTILINE)
126
126
  # Regex to parse locale files
@@ -192,14 +192,23 @@ def read_file(path, encoding="utf_8_sig"):
192
192
  return file.read()
193
193
 
194
194
 
195
- def parse_text(text, return_text_on_error=False, comments=False, dates=False, filename=None, is_global=False):
195
+ def parse_text(
196
+ text,
197
+ return_text_on_error=False,
198
+ comments=False,
199
+ dates=False,
200
+ as_object=False,
201
+ filename=None,
202
+ is_global=False,
203
+ ):
196
204
  """
197
205
  Parse raw text
198
206
  :param text: Text to parse
199
207
  :param return_text_on_error: (default false) Return working text document if parsing fails
200
208
  :param comments: (default false) Include comments?
201
209
  :param dates: (default false) Include dates?
202
- :param filename: (default none) Filename (only for debugging)
210
+ :param as_object: (default false) Return data as an object-like instead of dict
211
+ :param filename: (default none) file which is parsed (only for debugging)
203
212
  :param is_global: (default false) Are variables global?
204
213
  :return: Parsed data as dictionary
205
214
  """
@@ -503,7 +512,7 @@ def parse_text(text, return_text_on_error=False, comments=False, dates=False, fi
503
512
  logger.error(f"Parse error: {error}")
504
513
  logger.debug("Exception:", exc_info=True)
505
514
  return regex_clean.sub("", text) if return_text_on_error else None
506
- return root
515
+ return objectify(root) if as_object else root
507
516
 
508
517
 
509
518
  def parse_file(
@@ -514,6 +523,7 @@ def parse_file(
514
523
  save=False,
515
524
  comments=False,
516
525
  dates=False,
526
+ as_object=False,
517
527
  is_global=False,
518
528
  patch=None,
519
529
  ):
@@ -524,12 +534,16 @@ def parse_file(
524
534
  :param encoding: Encoding used to read file
525
535
  :param base_dir: Base directory (for debug)
526
536
  :param save: (default false) Save parsed file in output directory
527
- :param comments: Include comments?
528
- :param dates: Include dates?
537
+ :param comments: (default false) Include comments?
538
+ :param dates: (default false) Include dates?
539
+ :param as_object: (default false) Return data as an object-like instead of dict
529
540
  :param is_global: (default false) Are file's variables global?
530
541
  :param patch: String replacement patterns
531
542
  :return: Parsed data as dictionary or text if parsing fails
532
543
  """
544
+ if save and as_object:
545
+ logger.warning("`as_object` disabled bacause only dicts can be converted to JSON.")
546
+ as_object = False
533
547
  start_time = time.monotonic()
534
548
  if base_dir:
535
549
  base_dir = os.sep.join(str(base_dir).rstrip(os.sep).split(os.sep)[:-1]) + os.sep
@@ -543,7 +557,13 @@ def parse_file(
543
557
  filename = os.path.join(base_dir, os.path.basename(path))
544
558
  logger.debug(f"Parsing {filename}")
545
559
  data = parse_text(
546
- text, return_text_on_error=True, comments=comments, dates=dates, filename=filename, is_global=is_global
560
+ text,
561
+ return_text_on_error=True,
562
+ comments=comments,
563
+ dates=dates,
564
+ as_object=as_object,
565
+ filename=filename,
566
+ is_global=is_global,
547
567
  )
548
568
  if save:
549
569
  filename, _ = os.path.splitext(os.path.basename(path))
@@ -573,6 +593,7 @@ def parse_all_files(
573
593
  save=False,
574
594
  comments=False,
575
595
  dates=False,
596
+ as_object=False,
576
597
  variables_first=True,
577
598
  _variables_only=False,
578
599
  ):
@@ -583,8 +604,9 @@ def parse_all_files(
583
604
  :param encoding: Encoding used to read files
584
605
  :param keep_data: (default false) Return parsed data of all files in a dictionary
585
606
  :param save: (default false) Save every parsed data in output directory
586
- :param comments: Include comments?
587
- :param dates: Include dates?
607
+ :param comments: (default false) Include comments?
608
+ :param dates: (default false) Include dates?
609
+ :param as_object: (default false) Return data as an object-like instead of dict
588
610
  :param variables_first: Try to parse variables first
589
611
  :return: Dictionary (key: file, value: parsed data if keep_data=True)
590
612
  """
@@ -600,6 +622,7 @@ def parse_all_files(
600
622
  save=save,
601
623
  comments=comments,
602
624
  dates=dates,
625
+ as_object=as_object,
603
626
  variables_first=False,
604
627
  _variables_only=True,
605
628
  )
@@ -620,6 +643,7 @@ def parse_all_files(
620
643
  save=save,
621
644
  comments=comments,
622
645
  dates=dates,
646
+ as_object=as_object,
623
647
  is_global=_variables_only,
624
648
  )
625
649
  if isinstance(data, str):
@@ -691,6 +715,48 @@ def walk(obj, *from_keys):
691
715
  yield obj, from_keys
692
716
 
693
717
 
718
+ def objectify(data, name="Object", default=None):
719
+ """
720
+ Convert a dictionary to an object-like dictionary
721
+ :param data: dictionary
722
+ :param name: name of the object
723
+ :param default: default value for unknown attributes
724
+ :return: object-like dictionary
725
+ """
726
+
727
+ def _getattr(s, k):
728
+ try:
729
+ object.__getattribute__(s, k)
730
+ except AttributeError:
731
+ return s.get(k, default)
732
+
733
+ if isinstance(data, list):
734
+ return [
735
+ objectify(item)
736
+ for item in data
737
+ if not (isinstance(item, str) and item.startswith("@") and item.endswith("@"))
738
+ ]
739
+ elif isinstance(data, dict):
740
+ name = (data.get("@type") or name).title()
741
+ slots = set()
742
+ attrs = dict(__getattr__=lambda s, k: _getattr(s, k), __slots__=slots)
743
+ subdata = {}
744
+ for key, value in data.items():
745
+ if key.startswith("#"):
746
+ continue
747
+ skey = key.replace("@", "_").replace(":", "_")
748
+ if match := regex_date.match(skey):
749
+ skey = "_".join(match.groups())
750
+ skey = f"_{skey}" if key != skey or key[0].isdigit() else skey
751
+ slots.add(skey)
752
+ if isinstance(value, (list, dict)):
753
+ subdata[key] = subdata[skey] = objectify(value)
754
+ continue
755
+ subdata[key] = subdata[skey] = value
756
+ return type(name, (dict,), attrs)(subdata)
757
+ return data
758
+
759
+
694
760
  # Tags which are always a list when reverting
695
761
  list_keys_rules = [
696
762
  # Colors
@@ -737,6 +803,8 @@ def revert(obj, from_key=None, prev_key=None, depth=-1, sep="\t", sort=False):
737
803
  elif depth > 0:
738
804
  lines.append(f"{tabs}{{")
739
805
  for key, value in sorted(obj.items()) if sort else obj.items():
806
+ if key.startswith("_"):
807
+ continue
740
808
  lines.extend(revert(value, from_key=key, prev_key=from_key, depth=depth + 1, sep=sep, sort=sort))
741
809
  if from_key or depth > 0:
742
810
  lines.append(f"{tabs}}}")
@@ -884,6 +952,22 @@ def save_variables(filepath="_variables.json"):
884
952
  json_dump(global_variables, file, sort_keys=True)
885
953
 
886
954
 
955
+ def enable_logging(debug=False, file=False):
956
+ logger.setLevel(logging.DEBUG)
957
+ formatter = logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s")
958
+ console_handler = logging.StreamHandler()
959
+ console_handler.setLevel(logging.INFO)
960
+ console_handler.setFormatter(formatter)
961
+ logger.addHandler(console_handler)
962
+ if debug:
963
+ console_handler.setLevel(logging.DEBUG)
964
+ if file:
965
+ file_handler = logging.FileHandler("ckparser.log")
966
+ file_handler.setLevel(logging.INFO)
967
+ file_handler.setFormatter(formatter)
968
+ logger.addHandler(file_handler)
969
+
970
+
887
971
  def main():
888
972
  """
889
973
  Command-line main entrypoint
@@ -900,18 +984,7 @@ def main():
900
984
  parser.add_argument("--debug", action="store_true", help="debug mode?")
901
985
  args = parser.parse_args()
902
986
 
903
- logger.setLevel(logging.DEBUG)
904
- formatter = logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s")
905
- console_handler = logging.StreamHandler()
906
- console_handler.setLevel(logging.INFO)
907
- console_handler.setFormatter(formatter)
908
- logger.addHandler(console_handler)
909
- if args.debug:
910
- console_handler.setLevel(logging.DEBUG)
911
- file_handler = logging.FileHandler("ckparser.log")
912
- file_handler.setLevel(logging.INFO)
913
- file_handler.setFormatter(formatter)
914
- logger.addHandler(file_handler)
987
+ enable_logging(debug=args.debug, file=True)
915
988
 
916
989
  if args.revert:
917
990
  if os.path.isdir(args.path):
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ckparser"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Parse Paradox Jomini data files to Python/JSON and revert them back experimentally."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes