quick-ini 4.0__tar.gz → 4.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.
- {quick_ini-4.0/quick_ini.egg-info → quick_ini-4.3}/PKG-INFO +15 -3
- {quick_ini-4.0 → quick_ini-4.3}/README.md +14 -2
- {quick_ini-4.0 → quick_ini-4.3}/pyproject.toml +4 -1
- quick_ini-4.3/quick_ini/__main__.py +67 -0
- {quick_ini-4.0 → quick_ini-4.3}/quick_ini/quick_ini.py +24 -4
- {quick_ini-4.0 → quick_ini-4.3/quick_ini.egg-info}/PKG-INFO +15 -3
- {quick_ini-4.0 → quick_ini-4.3}/quick_ini.egg-info/SOURCES.txt +2 -0
- quick_ini-4.3/quick_ini.egg-info/entry_points.txt +2 -0
- {quick_ini-4.0 → quick_ini-4.3}/tests/test_quickini.py +127 -0
- {quick_ini-4.0 → quick_ini-4.3}/LICENSE +0 -0
- {quick_ini-4.0 → quick_ini-4.3}/quick_ini/__init__.py +0 -0
- {quick_ini-4.0 → quick_ini-4.3}/quick_ini/py.typed +0 -0
- {quick_ini-4.0 → quick_ini-4.3}/quick_ini.egg-info/dependency_links.txt +0 -0
- {quick_ini-4.0 → quick_ini-4.3}/quick_ini.egg-info/top_level.txt +0 -0
- {quick_ini-4.0 → quick_ini-4.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quick_ini
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3
|
|
4
4
|
Summary: A library for reading from and writing to .ini formatted files
|
|
5
5
|
Author-email: OllieZ-Mods <socksinthewash@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -38,7 +38,7 @@ pip install quick_ini
|
|
|
38
38
|
```python
|
|
39
39
|
from quick_ini import QuickIni
|
|
40
40
|
|
|
41
|
-
# Load from file
|
|
41
|
+
# Load from file, before any get_value() or get_all() (those raise RuntimeError otherwise)
|
|
42
42
|
QuickIni.load_file("config.ini")
|
|
43
43
|
|
|
44
44
|
# Load from URL
|
|
@@ -82,7 +82,19 @@ if report and report["added"]:
|
|
|
82
82
|
print("New settings added to config.ini:", ", ".join(report["added"]))
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
The report also has `created`, `changed`, `orphaned` (settings kept that aren't in the template) and `
|
|
85
|
+
The report also has `created`, `changed`, `orphaned` (settings kept that aren't in the template), `backup` and `text` (the synced file). Use `QuickIni.sync_template("config.ini")` to sync without loading, and `dry_run=True` to work out the changes without writing anything.
|
|
86
|
+
|
|
87
|
+
### From the command line
|
|
88
|
+
|
|
89
|
+
Installing QuickINI adds a `quick-ini` command (or run `python -m quick_ini`), so you can sync without starting your program:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
quick-ini sync # config.ini with config.ini.template
|
|
93
|
+
quick-ini sync --dry-run # show what would change as a diff, write nothing (--diff works too)
|
|
94
|
+
quick-ini sync --config app.ini --template defaults.ini
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`--config` defaults to `config.ini` and `--template` to `<config>.template`. Unlike `template=True`, a missing template is an error here. It prints the settings added and kept, and exits with 1 on any error.
|
|
86
98
|
|
|
87
99
|
## File Format
|
|
88
100
|
|
|
@@ -13,7 +13,7 @@ pip install quick_ini
|
|
|
13
13
|
```python
|
|
14
14
|
from quick_ini import QuickIni
|
|
15
15
|
|
|
16
|
-
# Load from file
|
|
16
|
+
# Load from file, before any get_value() or get_all() (those raise RuntimeError otherwise)
|
|
17
17
|
QuickIni.load_file("config.ini")
|
|
18
18
|
|
|
19
19
|
# Load from URL
|
|
@@ -57,7 +57,19 @@ if report and report["added"]:
|
|
|
57
57
|
print("New settings added to config.ini:", ", ".join(report["added"]))
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
The report also has `created`, `changed`, `orphaned` (settings kept that aren't in the template) and `
|
|
60
|
+
The report also has `created`, `changed`, `orphaned` (settings kept that aren't in the template), `backup` and `text` (the synced file). Use `QuickIni.sync_template("config.ini")` to sync without loading, and `dry_run=True` to work out the changes without writing anything.
|
|
61
|
+
|
|
62
|
+
### From the command line
|
|
63
|
+
|
|
64
|
+
Installing QuickINI adds a `quick-ini` command (or run `python -m quick_ini`), so you can sync without starting your program:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
quick-ini sync # config.ini with config.ini.template
|
|
68
|
+
quick-ini sync --dry-run # show what would change as a diff, write nothing (--diff works too)
|
|
69
|
+
quick-ini sync --config app.ini --template defaults.ini
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--config` defaults to `config.ini` and `--template` to `<config>.template`. Unlike `template=True`, a missing template is an error here. It prints the settings added and kept, and exits with 1 on any error.
|
|
61
73
|
|
|
62
74
|
## File Format
|
|
63
75
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quick_ini"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.3"
|
|
8
8
|
description = "A library for reading from and writing to .ini formatted files"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -25,6 +25,9 @@ classifiers = [
|
|
|
25
25
|
"Typing :: Typed",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
|
+
[project.scripts]
|
|
29
|
+
quick-ini = "quick_ini.__main__:main"
|
|
30
|
+
|
|
28
31
|
[project.urls]
|
|
29
32
|
Homepage = "https://github.com/olliez-mods/Quick-Ini"
|
|
30
33
|
"Bug Reports" = "https://github.com/olliez-mods/Quick-Ini/issues"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command line: quick-ini sync [--config config.ini] [--template <config>.template] [--dry-run | --diff]
|
|
3
|
+
\nAlso runs as "python -m quick_ini". Brings a config file up to date with its template, see QuickIni.sync_template().
|
|
4
|
+
"""
|
|
5
|
+
from .quick_ini import QuickIni
|
|
6
|
+
import argparse
|
|
7
|
+
import difflib
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
def sync(config:str, template:str | None, dry_run:bool) -> int:
|
|
11
|
+
# Unlike load_file(template=True), a missing template is an error here: on the command line it's a typo
|
|
12
|
+
template = template or f"{config}.template"
|
|
13
|
+
try:
|
|
14
|
+
with open(config, 'r', encoding='utf-8') as file:
|
|
15
|
+
old_text = file.read()
|
|
16
|
+
except FileNotFoundError:
|
|
17
|
+
old_text = ""
|
|
18
|
+
except IOError as e:
|
|
19
|
+
print(f"Error reading file: '{e}'", file=sys.stderr)
|
|
20
|
+
return 1
|
|
21
|
+
|
|
22
|
+
if(not QuickIni.sync_template(config, template, dry_run=dry_run)):
|
|
23
|
+
print(QuickIni.get_last_error(), file=sys.stderr)
|
|
24
|
+
return 1
|
|
25
|
+
report = QuickIni.get_sync_report()
|
|
26
|
+
|
|
27
|
+
if(dry_run and report["changed"]):
|
|
28
|
+
diff = difflib.unified_diff(old_text.splitlines(keepends=True), report["text"].splitlines(keepends=True),
|
|
29
|
+
config, f"{config} (synced)")
|
|
30
|
+
sys.stdout.writelines(diff)
|
|
31
|
+
print()
|
|
32
|
+
|
|
33
|
+
if(report["added"]):
|
|
34
|
+
print("Added from template:", ", ".join(report["added"]))
|
|
35
|
+
if(report["orphaned"]):
|
|
36
|
+
print("Kept at the end (not in template):", ", ".join(report["orphaned"]))
|
|
37
|
+
|
|
38
|
+
if(not report["changed"]):
|
|
39
|
+
print("Already in sync")
|
|
40
|
+
elif(dry_run):
|
|
41
|
+
print(f"Would create {config} from {template}" if report["created"] else "Dry run: nothing written")
|
|
42
|
+
elif(report["created"]):
|
|
43
|
+
print(f"Created {config} from {template}")
|
|
44
|
+
else:
|
|
45
|
+
print(f"Synced {config} (previous version: {report['backup']})")
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
def main(argv:list[str] | None = None) -> int:
|
|
49
|
+
parser = argparse.ArgumentParser(prog="quick-ini", description="Tools for .ini config files.")
|
|
50
|
+
commands = parser.add_subparsers(dest="command")
|
|
51
|
+
sync_parser = commands.add_parser("sync", help="bring a config file up to date with its template",
|
|
52
|
+
description="Bring a config file up to date with its template. Your values are "
|
|
53
|
+
"kept, new keys get the template's value, keys the template doesn't have are kept "
|
|
54
|
+
"at the end of their section. The previous version is saved as <config>.bak.")
|
|
55
|
+
sync_parser.add_argument("--config", default="config.ini", help="the config file (default: config.ini)")
|
|
56
|
+
sync_parser.add_argument("--template", help="the template (default: <config>.template)")
|
|
57
|
+
sync_parser.add_argument("--dry-run", "--diff", dest="dry_run", action="store_true",
|
|
58
|
+
help="show the changes as a diff, write nothing")
|
|
59
|
+
|
|
60
|
+
args = parser.parse_args(argv)
|
|
61
|
+
if(args.command == "sync"):
|
|
62
|
+
return sync(args.config, args.template, args.dry_run)
|
|
63
|
+
parser.print_help()
|
|
64
|
+
return 1
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
sys.exit(main())
|
|
@@ -17,6 +17,7 @@ class QuickIni:
|
|
|
17
17
|
error_message: str = ""
|
|
18
18
|
web_file: str | None = None
|
|
19
19
|
sync_report: dict[str, Any] | None = None
|
|
20
|
+
load_called: bool = False
|
|
20
21
|
|
|
21
22
|
@staticmethod
|
|
22
23
|
def grab_from_url(url:str, timeout:float = 10) -> bool:
|
|
@@ -159,7 +160,7 @@ class QuickIni:
|
|
|
159
160
|
raise
|
|
160
161
|
|
|
161
162
|
@staticmethod
|
|
162
|
-
def sync_template(file_location:str, template:bool | str = True) -> bool:
|
|
163
|
+
def sync_template(file_location:str, template:bool | str = True, dry_run:bool = False) -> bool:
|
|
163
164
|
"""
|
|
164
165
|
Brings a config file up to date with its template, keeping the values already set in the config.
|
|
165
166
|
\nThe template decides which keys exist, their order, sections and comments. Keys the config doesn't have yet get
|
|
@@ -170,6 +171,7 @@ class QuickIni:
|
|
|
170
171
|
file_location (str): The path of the config file.
|
|
171
172
|
template (bool | str): True to use "<config>.template", skipped quietly if it doesn't exist.
|
|
172
173
|
Or the path of the template file, which must exist.
|
|
174
|
+
dry_run (bool): Work out the synced file without writing it (or a backup). The report has it as "text".
|
|
173
175
|
Returns:
|
|
174
176
|
bool: True if successfull, False otherwise
|
|
175
177
|
Example:
|
|
@@ -265,7 +267,7 @@ class QuickIni:
|
|
|
265
267
|
changed = new_text != old_text
|
|
266
268
|
backup = None
|
|
267
269
|
|
|
268
|
-
if(changed):
|
|
270
|
+
if(changed and not dry_run):
|
|
269
271
|
try:
|
|
270
272
|
if(old_text is not None):
|
|
271
273
|
backup = f"{path}.bak"
|
|
@@ -281,6 +283,7 @@ class QuickIni:
|
|
|
281
283
|
"added": added if old_text is not None else [],
|
|
282
284
|
"orphaned": orphaned,
|
|
283
285
|
"backup": backup,
|
|
286
|
+
"text": new_text,
|
|
284
287
|
}
|
|
285
288
|
return True
|
|
286
289
|
|
|
@@ -291,12 +294,12 @@ class QuickIni:
|
|
|
291
294
|
Return:
|
|
292
295
|
dict[str, Any]: "created" (bool) the config was made from the template, "changed" (bool) the config was rewritten,
|
|
293
296
|
"added" (list) keys taken from the template, "orphaned" (list) keys kept that aren't in the template,
|
|
294
|
-
"backup" (str | None) the path of the backup of the previous config
|
|
297
|
+
"backup" (str | None) the path of the backup of the previous config, "text" (str) the synced file
|
|
295
298
|
Example:
|
|
296
299
|
>>> load_file("config.ini", template=True)
|
|
297
300
|
True
|
|
298
301
|
>>> get_sync_report()
|
|
299
|
-
{'created': False, 'changed': True, 'added': ['database.timeout'], 'orphaned': [], 'backup': '/app/config.ini.bak'}
|
|
302
|
+
{'created': False, 'changed': True, 'added': ['database.timeout'], 'orphaned': [], 'backup': '/app/config.ini.bak', 'text': '...'}
|
|
300
303
|
"""
|
|
301
304
|
if(QuickIni.sync_report is None):
|
|
302
305
|
return None
|
|
@@ -323,6 +326,8 @@ class QuickIni:
|
|
|
323
326
|
"""
|
|
324
327
|
QuickIni.parsed_ini = {}
|
|
325
328
|
QuickIni.auto_type_convert_g = auto_type_convert
|
|
329
|
+
# Set even if loading fails, the caller gets False and reading falls back to defaults
|
|
330
|
+
QuickIni.load_called = True
|
|
326
331
|
|
|
327
332
|
if(not QuickIni.sync_template(file_location, template)):
|
|
328
333
|
# sync_template sets the error message
|
|
@@ -411,6 +416,15 @@ class QuickIni:
|
|
|
411
416
|
QuickIni.loaded_file_path=file_location
|
|
412
417
|
return True
|
|
413
418
|
|
|
419
|
+
@staticmethod
|
|
420
|
+
def check_loaded(call:str) -> None:
|
|
421
|
+
"""
|
|
422
|
+
Raises a RuntimeError if load_file() was never called, so settings aren't silently read as defaults.
|
|
423
|
+
"""
|
|
424
|
+
if(not QuickIni.load_called):
|
|
425
|
+
QuickIni.error_message = f"QuickIni.{call} was called before QuickIni.load_file()"
|
|
426
|
+
raise(RuntimeError(QuickIni.error_message))
|
|
427
|
+
|
|
414
428
|
@staticmethod
|
|
415
429
|
def get_value(name:str, default:Any = None, expected_type:type | None = None, section:str | None = None) -> Any:
|
|
416
430
|
"""
|
|
@@ -423,6 +437,8 @@ class QuickIni:
|
|
|
423
437
|
section (str): The [section] the setting is in, None for keys above any section
|
|
424
438
|
Return:
|
|
425
439
|
Any: The value associated with the name
|
|
440
|
+
Raises:
|
|
441
|
+
RuntimeError: If load_file() was never called
|
|
426
442
|
Example:
|
|
427
443
|
>>> get_value("do_debug_logs", False)
|
|
428
444
|
True
|
|
@@ -431,6 +447,7 @@ class QuickIni:
|
|
|
431
447
|
"""
|
|
432
448
|
if(section):
|
|
433
449
|
name = f"{section}.{name}"
|
|
450
|
+
QuickIni.check_loaded(f"get_value({name!r})")
|
|
434
451
|
value:Any = QuickIni.parsed_ini.get(name, default)
|
|
435
452
|
if(expected_type != None):
|
|
436
453
|
if(not isinstance(value, expected_type)):
|
|
@@ -446,10 +463,13 @@ class QuickIni:
|
|
|
446
463
|
section (str): Only return the settings in this [section], with the section prefix removed
|
|
447
464
|
Return:
|
|
448
465
|
dict[str, Any]: The settings, keys in a section are given as "section.key" when no section is passed
|
|
466
|
+
Raises:
|
|
467
|
+
RuntimeError: If load_file() was never called
|
|
449
468
|
Example:
|
|
450
469
|
>>> get_all("database")
|
|
451
470
|
{'host': 'localhost', 'port': 5432}
|
|
452
471
|
"""
|
|
472
|
+
QuickIni.check_loaded(f"get_all({section!r})" if section else "get_all()")
|
|
453
473
|
if(not section):
|
|
454
474
|
return dict(QuickIni.parsed_ini)
|
|
455
475
|
prefix = f"{section}."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quick_ini
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3
|
|
4
4
|
Summary: A library for reading from and writing to .ini formatted files
|
|
5
5
|
Author-email: OllieZ-Mods <socksinthewash@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -38,7 +38,7 @@ pip install quick_ini
|
|
|
38
38
|
```python
|
|
39
39
|
from quick_ini import QuickIni
|
|
40
40
|
|
|
41
|
-
# Load from file
|
|
41
|
+
# Load from file, before any get_value() or get_all() (those raise RuntimeError otherwise)
|
|
42
42
|
QuickIni.load_file("config.ini")
|
|
43
43
|
|
|
44
44
|
# Load from URL
|
|
@@ -82,7 +82,19 @@ if report and report["added"]:
|
|
|
82
82
|
print("New settings added to config.ini:", ", ".join(report["added"]))
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
The report also has `created`, `changed`, `orphaned` (settings kept that aren't in the template) and `
|
|
85
|
+
The report also has `created`, `changed`, `orphaned` (settings kept that aren't in the template), `backup` and `text` (the synced file). Use `QuickIni.sync_template("config.ini")` to sync without loading, and `dry_run=True` to work out the changes without writing anything.
|
|
86
|
+
|
|
87
|
+
### From the command line
|
|
88
|
+
|
|
89
|
+
Installing QuickINI adds a `quick-ini` command (or run `python -m quick_ini`), so you can sync without starting your program:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
quick-ini sync # config.ini with config.ini.template
|
|
93
|
+
quick-ini sync --dry-run # show what would change as a diff, write nothing (--diff works too)
|
|
94
|
+
quick-ini sync --config app.ini --template defaults.ini
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`--config` defaults to `config.ini` and `--template` to `<config>.template`. Unlike `template=True`, a missing template is an error here. It prints the settings added and kept, and exits with 1 on any error.
|
|
86
98
|
|
|
87
99
|
## File Format
|
|
88
100
|
|
|
@@ -2,10 +2,12 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
quick_ini/__init__.py
|
|
5
|
+
quick_ini/__main__.py
|
|
5
6
|
quick_ini/py.typed
|
|
6
7
|
quick_ini/quick_ini.py
|
|
7
8
|
quick_ini.egg-info/PKG-INFO
|
|
8
9
|
quick_ini.egg-info/SOURCES.txt
|
|
9
10
|
quick_ini.egg-info/dependency_links.txt
|
|
11
|
+
quick_ini.egg-info/entry_points.txt
|
|
10
12
|
quick_ini.egg-info/top_level.txt
|
|
11
13
|
tests/test_quickini.py
|
|
@@ -8,6 +8,9 @@ import tempfile
|
|
|
8
8
|
import os
|
|
9
9
|
from unittest.mock import patch, mock_open
|
|
10
10
|
from quick_ini import QuickIni
|
|
11
|
+
from quick_ini import __main__ as cli
|
|
12
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
13
|
+
import io
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
class TestQuickIni(unittest.TestCase):
|
|
@@ -16,6 +19,7 @@ class TestQuickIni(unittest.TestCase):
|
|
|
16
19
|
"""Set up test fixtures before each test method."""
|
|
17
20
|
# Clear any previous state
|
|
18
21
|
QuickIni.parsed_ini = {}
|
|
22
|
+
QuickIni.load_called = False
|
|
19
23
|
QuickIni.loaded_file_path = None
|
|
20
24
|
QuickIni.auto_type_convert_g = True
|
|
21
25
|
QuickIni.error_message = ""
|
|
@@ -473,6 +477,7 @@ class TestQuickIniFormats(unittest.TestCase):
|
|
|
473
477
|
|
|
474
478
|
def setUp(self):
|
|
475
479
|
QuickIni.parsed_ini = {}
|
|
480
|
+
QuickIni.load_called = False
|
|
476
481
|
QuickIni.loaded_file_path = None
|
|
477
482
|
QuickIni.auto_type_convert_g = True
|
|
478
483
|
QuickIni.error_message = ""
|
|
@@ -496,6 +501,30 @@ class TestQuickIniFormats(unittest.TestCase):
|
|
|
496
501
|
with open(path, encoding='utf-8') as f:
|
|
497
502
|
return f.read()
|
|
498
503
|
|
|
504
|
+
# ===== LOADING FIRST =====
|
|
505
|
+
|
|
506
|
+
def test_get_value_before_load_raises(self):
|
|
507
|
+
with self.assertRaises(RuntimeError):
|
|
508
|
+
QuickIni.get_value("port", 8080)
|
|
509
|
+
self.assertEqual(QuickIni.get_last_error(), "QuickIni.get_value('port') was called before QuickIni.load_file()")
|
|
510
|
+
|
|
511
|
+
def test_get_value_section_before_load_names_key(self):
|
|
512
|
+
with self.assertRaises(RuntimeError):
|
|
513
|
+
QuickIni.get_value("port", section="database")
|
|
514
|
+
self.assertIn("'database.port'", QuickIni.get_last_error())
|
|
515
|
+
|
|
516
|
+
def test_get_all_before_load_raises(self):
|
|
517
|
+
with self.assertRaises(RuntimeError):
|
|
518
|
+
QuickIni.get_all()
|
|
519
|
+
with self.assertRaises(RuntimeError):
|
|
520
|
+
QuickIni.get_all("database")
|
|
521
|
+
self.assertIn("get_all('database')", QuickIni.get_last_error())
|
|
522
|
+
|
|
523
|
+
def test_failed_load_falls_back_to_defaults(self):
|
|
524
|
+
self.assertFalse(QuickIni.load_file("/no/such/config.ini"))
|
|
525
|
+
self.assertEqual(QuickIni.get_value("port", 8080), 8080)
|
|
526
|
+
self.assertEqual(QuickIni.get_all(), {})
|
|
527
|
+
|
|
499
528
|
# ===== PARSING =====
|
|
500
529
|
|
|
501
530
|
def test_spaces_around_equals(self):
|
|
@@ -667,6 +696,7 @@ port = 5432
|
|
|
667
696
|
|
|
668
697
|
def setUp(self):
|
|
669
698
|
QuickIni.parsed_ini = {}
|
|
699
|
+
QuickIni.load_called = False
|
|
670
700
|
QuickIni.loaded_file_path = None
|
|
671
701
|
QuickIni.auto_type_convert_g = True
|
|
672
702
|
QuickIni.error_message = ""
|
|
@@ -826,5 +856,102 @@ port = 6000
|
|
|
826
856
|
self.assertFalse(any(name.endswith(".tmp") for name in os.listdir(self.dir.name)))
|
|
827
857
|
|
|
828
858
|
|
|
859
|
+
|
|
860
|
+
class TestQuickIniCommandLine(unittest.TestCase):
|
|
861
|
+
"""Tests for "quick-ini sync" (quick_ini/__main__.py), run from inside a temp folder for the default paths."""
|
|
862
|
+
|
|
863
|
+
# The template tests' setup and helpers, without running those tests again
|
|
864
|
+
TEMPLATE = TestQuickIniTemplate.TEMPLATE
|
|
865
|
+
write = TestQuickIniTemplate.write
|
|
866
|
+
read = TestQuickIniTemplate.read
|
|
867
|
+
|
|
868
|
+
def setUp(self):
|
|
869
|
+
TestQuickIniTemplate.setUp(self)
|
|
870
|
+
self.cwd = os.getcwd()
|
|
871
|
+
os.chdir(self.dir.name)
|
|
872
|
+
|
|
873
|
+
def tearDown(self):
|
|
874
|
+
os.chdir(self.cwd)
|
|
875
|
+
TestQuickIniTemplate.tearDown(self)
|
|
876
|
+
|
|
877
|
+
def run_cli(self, *args):
|
|
878
|
+
out, err = io.StringIO(), io.StringIO()
|
|
879
|
+
with redirect_stdout(out), redirect_stderr(err):
|
|
880
|
+
code = cli.main(list(args))
|
|
881
|
+
return code, out.getvalue(), err.getvalue()
|
|
882
|
+
|
|
883
|
+
def test_default_config_and_template(self):
|
|
884
|
+
self.write("config.ini.template", self.TEMPLATE)
|
|
885
|
+
self.write("config.ini", "debug = true\nold = 1\n")
|
|
886
|
+
code, out, _ = self.run_cli("sync")
|
|
887
|
+
self.assertEqual(code, 0)
|
|
888
|
+
self.assertIn("debug = true", self.read(self.config))
|
|
889
|
+
self.assertIn("name = MyApp", self.read(self.config))
|
|
890
|
+
self.assertIn("Added from template: name, parts, database.host, database.port", out)
|
|
891
|
+
self.assertIn("Kept at the end (not in template): old", out)
|
|
892
|
+
self.assertIn("previous version:", out)
|
|
893
|
+
self.assertEqual(self.read(self.config + ".bak"), "debug = true\nold = 1\n")
|
|
894
|
+
|
|
895
|
+
def test_config_and_template_given(self):
|
|
896
|
+
self.write("defaults.ini", self.TEMPLATE)
|
|
897
|
+
self.write("app.ini", "debug = true\n")
|
|
898
|
+
code, _, _ = self.run_cli("sync", "--config", "app.ini", "--template", "defaults.ini")
|
|
899
|
+
self.assertEqual(code, 0)
|
|
900
|
+
self.assertIn("name = MyApp", self.read(os.path.join(self.dir.name, "app.ini")))
|
|
901
|
+
|
|
902
|
+
def test_template_defaults_to_given_config(self):
|
|
903
|
+
self.write("app.ini.template", self.TEMPLATE)
|
|
904
|
+
self.write("app.ini", "debug = true\n")
|
|
905
|
+
code, _, _ = self.run_cli("sync", "--config", "app.ini")
|
|
906
|
+
self.assertEqual(code, 0)
|
|
907
|
+
self.assertIn("name = MyApp", self.read(os.path.join(self.dir.name, "app.ini")))
|
|
908
|
+
|
|
909
|
+
def test_dry_run_shows_diff_and_writes_nothing(self):
|
|
910
|
+
for flag in ("--dry-run", "--diff"):
|
|
911
|
+
self.write("config.ini.template", self.TEMPLATE)
|
|
912
|
+
self.write("config.ini", "debug = true\n")
|
|
913
|
+
code, out, _ = self.run_cli("sync", flag)
|
|
914
|
+
self.assertEqual(code, 0)
|
|
915
|
+
self.assertEqual(self.read(self.config), "debug = true\n")
|
|
916
|
+
self.assertFalse(os.path.exists(self.config + ".bak"))
|
|
917
|
+
self.assertIn("+++ config.ini (synced)", out)
|
|
918
|
+
self.assertIn("+name = MyApp", out)
|
|
919
|
+
self.assertIn("Dry run: nothing written", out)
|
|
920
|
+
|
|
921
|
+
def test_dry_run_does_not_create_config(self):
|
|
922
|
+
self.write("config.ini.template", self.TEMPLATE)
|
|
923
|
+
code, out, _ = self.run_cli("sync", "--dry-run")
|
|
924
|
+
self.assertEqual(code, 0)
|
|
925
|
+
self.assertFalse(os.path.exists(self.config))
|
|
926
|
+
self.assertIn("Would create config.ini from config.ini.template", out)
|
|
927
|
+
|
|
928
|
+
def test_missing_config_is_created(self):
|
|
929
|
+
self.write("config.ini.template", self.TEMPLATE)
|
|
930
|
+
code, out, _ = self.run_cli("sync")
|
|
931
|
+
self.assertEqual(code, 0)
|
|
932
|
+
self.assertEqual(self.read(self.config), self.TEMPLATE)
|
|
933
|
+
self.assertIn("Created config.ini from config.ini.template", out)
|
|
934
|
+
|
|
935
|
+
def test_already_in_sync(self):
|
|
936
|
+
self.write("config.ini.template", self.TEMPLATE)
|
|
937
|
+
self.write("config.ini", self.TEMPLATE)
|
|
938
|
+
code, out, _ = self.run_cli("sync", "--dry-run")
|
|
939
|
+
self.assertEqual(code, 0)
|
|
940
|
+
self.assertEqual(out.strip(), "Already in sync")
|
|
941
|
+
|
|
942
|
+
def test_missing_template_is_an_error(self):
|
|
943
|
+
self.write("config.ini", "debug = true\n")
|
|
944
|
+
code, out, err = self.run_cli("sync")
|
|
945
|
+
self.assertEqual(code, 1)
|
|
946
|
+
self.assertIn("Could not find template", err)
|
|
947
|
+
self.assertEqual(out, "")
|
|
948
|
+
self.assertEqual(self.read(self.config), "debug = true\n")
|
|
949
|
+
|
|
950
|
+
def test_no_command_prints_help(self):
|
|
951
|
+
code, out, _ = self.run_cli()
|
|
952
|
+
self.assertEqual(code, 1)
|
|
953
|
+
self.assertIn("sync", out)
|
|
954
|
+
|
|
955
|
+
|
|
829
956
|
if __name__ == '__main__':
|
|
830
957
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|