ckparser 0.1__py3-none-any.whl

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.
@@ -0,0 +1,536 @@
1
+ Metadata-Version: 2.4
2
+ Name: ckparser
3
+ Version: 0.1
4
+ Summary: Parse Paradox Jomini data files to Python/JSON and revert them back experimentally.
5
+ Author: Marc Debureaux (debnet)
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/debnet/ckparser
8
+ Project-URL: Repository, https://github.com/debnet/ckparser
9
+ Project-URL: Issues, https://github.com/debnet/ckparser/issues
10
+ Keywords: paradox,jomini,modding,crusader-kings,europa-universalis,parser,json
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Text Processing :: General
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: detect-encoding
29
+ Requires-Dist: chardet>=5; extra == "detect-encoding"
30
+ Dynamic: license-file
31
+
32
+ # ckparser
33
+
34
+ `ckparser` is a small Python utility for modding Paradox games that use the **Jomini** data format, such as *Crusader Kings III* or *Europa Universalis V*.
35
+
36
+ Its purpose is to convert Jomini data as faithfully as possible into Python data structures (`dict`, `list`, and native scalar types), and, when possible, convert Python/JSON data back into Jomini text.
37
+
38
+ The project can be used in two main ways:
39
+
40
+ - as a **command-line tool** to parse files or directories;
41
+ - as a **Python library** inside personal modding scripts.
42
+
43
+ It is primarily intended for modders who want to inspect, transform, export, or automate work on Paradox script data.
44
+
45
+ ## Features
46
+
47
+ ### Jomini → Python / JSON
48
+
49
+ - parse raw Jomini text into Python data structures;
50
+ - parse a single Jomini file into a dictionary;
51
+ - parse all `.txt` files in a directory recursively;
52
+ - optionally preserve comments;
53
+ - optionally detect file encodings automatically with `chardet`;
54
+ - partially resolve variables and inline formulas;
55
+ - collect global variables from `script_values` before parsing the rest of a mod directory.
56
+
57
+ ### Python / JSON → Jomini
58
+
59
+ - convert Python dictionaries back into Jomini text;
60
+ - revert a JSON file into a Jomini-style text file;
61
+ - apply heuristic rules to decide whether a structure should be rendered as a list, a block, or repeated key/value pairs.
62
+
63
+ > **Important**
64
+ > Reverse conversion is currently **experimental**. It can work well on many simple or moderately structured cases, but it cannot guarantee a perfect reconstruction of all Jomini files.
65
+
66
+ ### Utilities
67
+
68
+ - convert `HSV` / `HLS` colors to `RGB`;
69
+ - convert Jomini dates (`YYYY.MM.DD`) to Python `datetime.date`;
70
+ - read files safely with encoding detection;
71
+ - walk deeply nested structures;
72
+ - parse Paradox localization files (`.yml`).
73
+
74
+ ---
75
+
76
+ ## Installation
77
+
78
+ ### From PyPI
79
+
80
+ ```bash
81
+ pip install ckparser
82
+ ```
83
+
84
+ ### Optional dependency
85
+
86
+ ckparser does not require external dependencies to run, but it can use chardet for automatic encoding detection.
87
+
88
+ ```bash
89
+ pip install chardet
90
+ ```
91
+
92
+ Without `chardet`, the parser still works, but encoding detection is disabled.
93
+
94
+ ## Command-line usage
95
+
96
+ The module can be executed directly with:
97
+
98
+ ```bash
99
+ python -m ckparser <path>
100
+ ```
101
+
102
+ ### Help
103
+
104
+ ```
105
+ usage: ckparser.py [-h] [--encoding ENCODING] [--output OUTPUT] [--revert] [--comments] [--debug] path
106
+
107
+ Parse data from Paradox files in JSON or revert JSON files to Paradox format
108
+
109
+ positional arguments:
110
+ path path to a file or a directory to parse/revert
111
+
112
+ options:
113
+ -h, --help show this help message and exit
114
+ --encoding ENCODING encoding for reading/writing files
115
+ --output OUTPUT output directory for parsing results
116
+ --revert revert JSON files?
117
+ --comments include comments?
118
+ --debug debug mode?
119
+ ```
120
+
121
+ ### Examples
122
+
123
+ Parse a single Jomini file:
124
+
125
+ ```bash
126
+ python -m ckparser common/culture/cultures/my_cultures.txt
127
+ ```
128
+
129
+ Parse a directory recursively and write JSON output:
130
+
131
+ ```bash
132
+ python -m ckparser my_mod/common --output output
133
+ ```
134
+
135
+ Include comments in the parsed output:
136
+
137
+ ```bash
138
+ python -m ckparser my_mod/common --comments
139
+ ```
140
+
141
+ Revert a JSON file back to Jomini format:
142
+
143
+ ```bash
144
+ python -m ckparser data.json --revert
145
+ ```
146
+
147
+ Enable debug logging:
148
+
149
+ ```bash
150
+ python -m ckparser my_mod/common --debug
151
+ ```
152
+
153
+ ### Output behavior
154
+
155
+ * in parsing mode, each `.txt` file can be converted to `.json`;
156
+ * if parsing fails, the intermediate processed text can be saved as a `.error` file for debugging;
157
+ * a `ckparser.log` file is generated;
158
+ * collected global variables can be saved to `_variables.json`.
159
+
160
+ ## Library usage
161
+
162
+ ### Import
163
+
164
+ ```python
165
+ import ckparser
166
+ ```
167
+
168
+ ### `parse_text`
169
+
170
+ Convert raw Jomini text into a Python structure.
171
+
172
+ ```python
173
+ from ckparser import parse_text
174
+
175
+ text = """
176
+ my_trigger = {
177
+ has_trait = brave
178
+ age >= 16
179
+ }
180
+ """
181
+
182
+ data = parse_text(text)
183
+ print(data)
184
+ ```
185
+
186
+ ### `parse_file`
187
+
188
+ Convert a Jomini file into a Python dictionary.
189
+
190
+ ```python
191
+ from ckparser import parse_file
192
+
193
+ data = parse_file("common/scripted_triggers/my_triggers.txt")
194
+ ```
195
+
196
+ ### `parse_all_files`
197
+
198
+ Parse all .txt files in a directory recursively.
199
+
200
+ ```python
201
+ from ckparser import parse_all_files
202
+
203
+ result = parse_all_files("my_mod/common", keep_data=True)
204
+ ```
205
+
206
+ Exmple with JSON export:
207
+
208
+ ```python
209
+ result = parse_all_files(
210
+ "my_mod/common",
211
+ output_dir="output",
212
+ save=True,
213
+ keep_data=False,
214
+ )
215
+ ```
216
+
217
+ ### `revert`
218
+
219
+ Convert a Python structure back into Jomini text.
220
+
221
+ ```python
222
+ from ckparser import revert
223
+
224
+ data = {
225
+ "my_effect": {
226
+ "add_prestige": 100
227
+ }
228
+ }
229
+
230
+ text = revert(data)
231
+ print(text)
232
+ ```
233
+
234
+ ### `revert_file`
235
+
236
+ Convert a JSON file into a Jomini text file.
237
+
238
+ ```python
239
+ from ckparser import revert_file
240
+
241
+ text = revert_file("my_data.json", save=True, output_dir="output")
242
+ ```
243
+
244
+ ### `convert_color`
245
+
246
+ Convert supported color notations into an RGB hex string.
247
+
248
+ ```python
249
+ from ckparser import convert_color
250
+
251
+ print(convert_color(["hsv", 0.5, 0.8, 0.9]))
252
+ print(convert_color(["hsv360", 180, 80, 90]))
253
+ print(convert_color(["rgb", 255, 128, 0]))
254
+ ```
255
+
256
+ ### `convert_date`
257
+
258
+ Convert a Jomini date into datetime.date.
259
+
260
+ ```python
261
+ from ckparser import convert_date
262
+
263
+ date = convert_date("1066.9.15")
264
+ print(date)
265
+ ```
266
+
267
+ ### `read_file`
268
+
269
+ Read a file using the most appropriate encoding.
270
+
271
+ ```python
272
+ from ckparser import read_file
273
+
274
+ content = read_file("common/landed_titles/00_landed_titles.txt")
275
+ ```
276
+
277
+ ### `walk`
278
+
279
+ Traverse a complex nested structure and yield terminal values with their logical path.
280
+
281
+ ```python
282
+ from ckparser import walk
283
+
284
+ for value, path in walk(data):
285
+ print(path, value)
286
+ ```
287
+
288
+ ### `parse_all_locales`
289
+
290
+ Parse Paradox localization files.
291
+
292
+ ```python
293
+ from ckparser import parse_all_locales
294
+
295
+ locales = parse_all_locales("localization", language="english")
296
+ print(locales.get("my_key"))
297
+ ```
298
+
299
+ ## Data model and design choices
300
+
301
+ Jomini is flexible, ambiguous, and not always internally consistent from the perspective of conventional programming data structures. ckparser therefore makes a number of practical decisions to produce useful Python output.
302
+
303
+ ### 1. A `{}` block may represent either a dictionary or a list
304
+
305
+ In Jomini, the same block syntax can represent:
306
+
307
+ * a dictionary-like structure;
308
+ * a list of plain values;
309
+ * a list of nested blocks;
310
+ * or, in some cases, a mixed structure.
311
+
312
+ The parser attempts to infer the most reasonable Python representation from context.
313
+
314
+ ### 2. Duplicate keys are converted into lists
315
+
316
+ In Jomini, the same key may appear multiple times inside the same block:
317
+
318
+ ```
319
+ modifier = { factor = 2 }
320
+ modifier = { factor = 3 }
321
+ ```
322
+
323
+ Since Python dictionaries cannot store duplicate keys, `ckparser` converts the value into a list:
324
+
325
+ ```python
326
+ {
327
+ "modifier": [
328
+ {"factor": 2},
329
+ {"factor": 3}
330
+ ]
331
+ }
332
+ ```
333
+
334
+ This is especially useful for structures involving repeated logical operators such as `if`, `else_if`, `or`, `and`, and similar constructs.
335
+
336
+ ### 3. Non-standard operators are stored explicitly
337
+
338
+ Jomini supports operators such as:
339
+
340
+ * `=` (for affectation and comparison)
341
+ * `!=`
342
+ * `>`
343
+ * `<`
344
+ * `>=`
345
+ * `<=`
346
+ * `?=` (exists = ...)
347
+ * ... and others
348
+
349
+ When an operator other than = is encountered, ckparser stores it explicitly:
350
+
351
+ ```python
352
+ {
353
+ "age": {
354
+ "@operator": ">=",
355
+ "@value": 16
356
+ }
357
+ }
358
+ ```
359
+
360
+ This makes the original condition easier to preserve and inspect.
361
+
362
+ ### 4. Variables and formulas
363
+
364
+ Jomini supports variable references and inline formulas, for example with `@var` or `@[ ... ]`.
365
+
366
+ `ckparser` attempts to preserve:
367
+
368
+ * the original expression;
369
+ * the semantic type (`variable` or `formula`);
370
+ * the evaluated result, when available.
371
+
372
+ Example:
373
+
374
+ ```json
375
+ {
376
+ "some_value": {
377
+ "@type": "variable",
378
+ "@value": "@my_var",
379
+ "@result": 42
380
+ }
381
+ }
382
+ ```
383
+
384
+ Or:
385
+
386
+ ```json
387
+ {
388
+ "scaled_value": {
389
+ "@type": "formula",
390
+ "@value": "@[base_value * 2]",
391
+ "@result": 84
392
+ }
393
+ }
394
+ ```
395
+
396
+ ### 5. Global variables and parsing order
397
+
398
+ Variable resolution often depends on:
399
+
400
+ * mod structure;
401
+ * file loading order;
402
+ * definitions stored in `script_values`;
403
+ * previously collected values.
404
+
405
+ For that reason, `parse_all_files()` can first parse files from `script_values` and register them as global variables before parsing the rest of the directory.
406
+
407
+ ### Comments
408
+
409
+ By default, comments are removed during parsing.
410
+
411
+ With `comments=True` or `--comments`, the parser attempts to preserve them in an internal technical representation so they remain available for debugging or later processing.
412
+
413
+ Comment preservation should be considered practical rather than perfectly lossless.
414
+
415
+ ### Localization parsing
416
+
417
+ `ckparser` also includes a dedicated parser for Paradox localization files:
418
+
419
+ ```python
420
+ from ckparser import parse_all_locales
421
+
422
+ locales = parse_all_locales("localization", language="english")
423
+ ```
424
+
425
+ This supports:
426
+
427
+ * parsing a single `.yml` file or an entire directory;
428
+ * selecting a target language;
429
+ * building a `{key: value}` dictionary.
430
+
431
+ The result can also be saved as JSON.
432
+
433
+ ### Example
434
+
435
+ #### Jomini input
436
+
437
+ ```
438
+ my_entry = {
439
+ name = "Example"
440
+ age >= 16
441
+ is_active = yes
442
+ values = { 1 2 3 }
443
+ }
444
+ ```
445
+
446
+ #### Python output
447
+
448
+ ```python
449
+ {
450
+ "my_entry": {
451
+ "name": "Example",
452
+ "age": {
453
+ "@operator": ">=",
454
+ "@value": 16
455
+ },
456
+ "is_active": True,
457
+ "values": [1, 2, 3]
458
+ }
459
+ }
460
+ ```
461
+
462
+ #### JSON export
463
+
464
+ ```python
465
+ import json
466
+ from ckparser import parse_file
467
+
468
+ data = parse_file("example.txt")
469
+ print(json.dumps(data, indent=4, ensure_ascii=False))
470
+ ```
471
+
472
+ ## Known limitations
473
+
474
+ ### Jomini is inherently ambiguous
475
+
476
+ Some Jomini blocks mix list-like and dictionary-like behavior in ways that cannot be represented perfectly in Python without making assumptions.
477
+
478
+ ### Reverse conversion is heuristic
479
+
480
+ The reverse transformation back to Jomini is still heuristic. It works on many straightforward cases, but it may not:
481
+
482
+ * reconstruct the exact original syntax;
483
+ * know whether a structure should be rendered as a list or as repeated keys;
484
+ * match the exact conventions expected by a specific game subsystem without additional rules.
485
+
486
+ ### Variable resolution depends on context
487
+
488
+ Formula and variable evaluation depends on:
489
+
490
+ * which files have already been parsed;
491
+ * what variables are known;
492
+ * the order in which parsing occurs;
493
+ * the mod’s internal structure.
494
+
495
+ As a result, some `@result` values may be missing or incomplete if the necessary context is not yet available.
496
+
497
+ ### Edge cases in Paradox scripting
498
+
499
+ Paradox scripting languages include many practical exceptions, formatting variants, and subsystem-specific conventions. ckparser aims to cover the most useful general cases, but not every possible edge case.
500
+
501
+ ## Typical use cases
502
+
503
+ * analyzing a mod programmatically;
504
+ * exporting Paradox files to JSON for inspection or diffing;
505
+ * writing migration or validation scripts;
506
+ * extracting data from landed_titles, script_values, events, and similar files;
507
+ * building personal Python tooling for Paradox modding workflows.
508
+
509
+ ## API summary
510
+
511
+ ### Parsing
512
+
513
+ * `parse_text(text, return_text_on_error=False, comments=False, filename=None, is_global=False)`
514
+ * `parse_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False, comments=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, variables_first=True)`
516
+
517
+ ### Reversion
518
+
519
+ * `revert(obj, from_key=None, prev_key=None, depth=-1, sep="\t", sort=False)`
520
+ * `revert_file(path, output_dir=None, encoding="utf_8_sig", base_dir=None, save=False)`
521
+
522
+ ### Utilities
523
+
524
+ * `convert_color(color)`
525
+ * `convert_date(date, key=None)`
526
+ * `read_file(path, encoding="utf_8_sig")`
527
+ * `walk(obj, *from_keys)`
528
+ * `parse_all_locales(path, encoding="utf_8_sig", language="english", save=False)`
529
+ * `load_variables(filepath="_variables.json")`
530
+ * `save_variables(filepath="_variables.json")`
531
+
532
+ ## Project status
533
+
534
+ `ckparser` is intended as a practical modding tool rather than a formal reference implementation of the Jomini format. The parser is already useful for real automation tasks, while some parts, especially reverse conversion, remain intentionally pragmatic and experimental.
535
+
536
+ Real-world examples, edge cases, and mod-specific rules are valuable for improving coverage over time.
@@ -0,0 +1,7 @@
1
+ ckparser.py,sha256=Co6bNPxq9WTuKNxrORg7clKCmdm0KZa5yNVEHjKht5k,38564
2
+ ckparser-0.1.dist-info/licenses/LICENSE,sha256=VlnW-ZX-vhvQyCqd8ar5Lrp9lbYPgTVmlBqwu2cixuw,1101
3
+ ckparser-0.1.dist-info/METADATA,sha256=casBe8-W0zshINo-M-A7LYFvS4fiuS1UbNUmyqjWZMc,14130
4
+ ckparser-0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ ckparser-0.1.dist-info/entry_points.txt,sha256=Tu07ZJcK06dmD6kK4vy3y4sZFAYYAvmpS7CGQe9ytwM,43
6
+ ckparser-0.1.dist-info/top_level.txt,sha256=PwST6-QHw3cO0z8B9Oy6w7oC4hETUtsqBYzFGj7h74g,9
7
+ ckparser-0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ckparser = ckparser:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Marc Debureaux (debnet)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ckparser