commented-configparser 2.2.0__tar.gz → 3.0.0__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.
Files changed (38) hide show
  1. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/.github/workflows/pypi-publish.yml +19 -14
  2. commented_configparser-3.0.0/.github/workflows/pypi-test-publish.yml +62 -0
  3. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/.github/workflows/python-tests.yml +8 -8
  4. commented_configparser-3.0.0/.pre-commit-config.yaml +11 -0
  5. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/CONTRIBUTING.md +0 -9
  6. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/PKG-INFO +16 -10
  7. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/README.md +14 -8
  8. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/noxfile.py +22 -24
  9. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/pyproject.toml +1 -1
  10. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commented_configparser.egg-info/PKG-INFO +16 -10
  11. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commented_configparser.egg-info/SOURCES.txt +3 -0
  12. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commentedconfigparser/commentedconfigparser.py +36 -18
  13. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/commentedconfigparser_test.py +10 -7
  14. commented_configparser-3.0.0/tests/empty_comments_expected.ini +17 -0
  15. commented_configparser-3.0.0/tests/empty_comments_input.ini +17 -0
  16. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/header_expected.ini +0 -1
  17. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/multi_expected.ini +0 -1
  18. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/pydocs_expected.ini +2 -2
  19. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/regression_original_expected.ini +3 -2
  20. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/regression_original_input.ini +1 -0
  21. commented_configparser-2.2.0/.pre-commit-config.yaml +0 -32
  22. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/.flake8 +0 -0
  23. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/.gitignore +0 -0
  24. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/LICENSE +0 -0
  25. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/requirements/constraints.txt +0 -0
  26. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/requirements/requirements-dev.txt +0 -0
  27. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/requirements/requirements-test.txt +0 -0
  28. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/requirements/requirements.txt +0 -0
  29. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/setup.cfg +0 -0
  30. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commented_configparser.egg-info/dependency_links.txt +0 -0
  31. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commented_configparser.egg-info/requires.txt +0 -0
  32. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commented_configparser.egg-info/top_level.txt +0 -0
  33. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commentedconfigparser/__init__.py +0 -0
  34. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/src/commentedconfigparser/py.typed +0 -0
  35. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/header_input.ini +0 -0
  36. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/multi01_input.ini +0 -0
  37. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/multi02_input.ini +0 -0
  38. {commented_configparser-2.2.0 → commented_configparser-3.0.0}/tests/pydocs_input.ini +0 -0
@@ -1,9 +1,9 @@
1
1
  name: "Publish Python distribution to PyPI"
2
2
  # Uses:
3
- # https://github.com/actions/setup-python : 0b93645e9fea7318ecaed2b359559ac225c90a2b
3
+ # https://github.com/actions/setup-python : a26af69be951a213d495a4c3e4e4022e16d87065
4
4
  # https://github.com/actions/checkout : 11bd71901bbe5b1630ceea73d27597364c9af683
5
- # https://github.com/actions/download-artifact : fa0a91b85d4f404e444e00e005971372dc801d16
6
- # https://github.com/actions/upload-artifact : 6f51ac03b9356f520e9adb1b1b7802705f340c2b
5
+ # https://github.com/actions/download-artifact : d3f86a106a0bac45b974a628896c90dbdf5c8093
6
+ # https://github.com/actions/upload-artifact : ea165f8d65b6e75b540449e92b4886f43607fa02
7
7
  # https://github.com/actions/pypa/gh-action-pypi-publish : 76f52bc884231f62b9a034ebfe128415bbaabdfc
8
8
 
9
9
  on:
@@ -12,8 +12,10 @@ on:
12
12
 
13
13
  jobs:
14
14
  build:
15
- name: "Build distribution"
15
+ name: "Build package"
16
16
  runs-on: "ubuntu-latest"
17
+ outputs:
18
+ artifact-id: "${{ steps.upload-artifact.outputs.artifact-id }}"
17
19
 
18
20
  steps:
19
21
  - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
@@ -21,36 +23,39 @@ jobs:
21
23
  persist-credentials: false
22
24
 
23
25
  - name: "Set up Python"
24
- uses: "actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b"
26
+ uses: "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065"
25
27
  with:
26
28
  python-version: "3.12"
27
29
 
28
30
  - name: "Build the package"
29
- run: "python -m pip install nox; nox --session build"
31
+ run: "python -m pip install nox; nox -s build"
30
32
 
31
33
  - name: "Store the distribution packages"
32
- uses: "actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b"
34
+ id: "upload-artifact"
35
+ uses: "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"
33
36
  with:
34
- name: "python-package-distributions"
35
- path: "dist/"
37
+ path: "./dist"
36
38
 
37
39
  publish-to-pypi:
38
40
  name: "Publish Python Distribution to PyPI"
39
- if: startsWith(github.ref, 'refs/tags/')
40
41
  needs: ["build"]
41
42
  runs-on: "ubuntu-latest"
42
43
  environment:
43
44
  name: "pypi"
44
- url: "https://pypi.org/p/commented-configparser"
45
+ url: "https://pypi.org/project/commented-configparser/"
45
46
  permissions:
46
47
  id-token: "write"
47
48
 
48
49
  steps:
49
50
  - name: "Download all the dists"
50
- uses: "actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16"
51
+ uses: "actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093"
51
52
  with:
52
- name: "python-package-distributions"
53
- path: "dist/"
53
+ artifact-ids: "${{ needs.build.outputs.artifact-id }}"
54
+ path: "./dist"
55
+ merge-multiple: true
54
56
 
55
57
  - name: "Publish distribution to PyPI"
56
58
  uses: "pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc"
59
+ with:
60
+ repository-url: "https://upload.pypi.org/legacy/"
61
+ verbose: true
@@ -0,0 +1,62 @@
1
+ name: "Publish Python distribution to PyPI Test"
2
+ # Uses:
3
+ # https://github.com/actions/setup-python : a26af69be951a213d495a4c3e4e4022e16d87065
4
+ # https://github.com/actions/checkout : 11bd71901bbe5b1630ceea73d27597364c9af683
5
+ # https://github.com/actions/download-artifact : d3f86a106a0bac45b974a628896c90dbdf5c8093
6
+ # https://github.com/actions/upload-artifact : ea165f8d65b6e75b540449e92b4886f43607fa02
7
+ # https://github.com/actions/pypa/gh-action-pypi-publish : 76f52bc884231f62b9a034ebfe128415bbaabdfc
8
+
9
+ on:
10
+ push:
11
+ tags:
12
+ - "[0-9]+.[0-9]+.[a-z]+[0-9]+"
13
+
14
+ jobs:
15
+ build:
16
+ name: "Build package"
17
+ runs-on: "ubuntu-latest"
18
+ outputs:
19
+ artifact-id: "${{ steps.upload-artifact.outputs.artifact-id }}"
20
+
21
+ steps:
22
+ - uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - name: "Set up Python"
27
+ uses: "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065"
28
+ with:
29
+ python-version: "3.12"
30
+
31
+ - name: "Build the package"
32
+ run: "python -m pip install nox; nox -s build"
33
+
34
+ - name: "Store the distribution packages"
35
+ id: "upload-artifact"
36
+ uses: "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"
37
+ with:
38
+ path: "./dist"
39
+
40
+ publish-to-pypi:
41
+ name: "Publish Python Distribution to PyPI"
42
+ needs: ["build"]
43
+ runs-on: "ubuntu-latest"
44
+ environment:
45
+ name: "testpypi"
46
+ url: "https://test.pypi.org/project/commented-configparser/"
47
+ permissions:
48
+ id-token: "write"
49
+
50
+ steps:
51
+ - name: "Download all the dists"
52
+ uses: "actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093"
53
+ with:
54
+ artifact-ids: "${{ needs.build.outputs.artifact-id }}"
55
+ path: "./dist"
56
+ merge-multiple: true
57
+
58
+ - name: "Publish distribution to PyPI"
59
+ uses: "pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc"
60
+ with:
61
+ repository-url: "https://test.pypi.org/legacy/"
62
+ verbose: true
@@ -1,9 +1,9 @@
1
1
  name: "python tests and coverage"
2
2
  # Uses:
3
- # https://github.com/actions/setup-python : 0b93645e9fea7318ecaed2b359559ac225c90a2b
3
+ # https://github.com/actions/setup-python : a26af69be951a213d495a4c3e4e4022e16d87065
4
4
  # https://github.com/actions/checkout : 11bd71901bbe5b1630ceea73d27597364c9af683
5
- # https://github.com/actions/download-artifact : fa0a91b85d4f404e444e00e005971372dc801d16
6
- # https://github.com/actions/upload-artifact : 6f51ac03b9356f520e9adb1b1b7802705f340c2b
5
+ # https://github.com/actions/download-artifact : d3f86a106a0bac45b974a628896c90dbdf5c8093
6
+ # https://github.com/actions/upload-artifact : ea165f8d65b6e75b540449e92b4886f43607fa02
7
7
 
8
8
  on:
9
9
  pull_request:
@@ -46,7 +46,7 @@ jobs:
46
46
  uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
47
47
 
48
48
  - name: "Set up Python ${{ matrix.python-version }}"
49
- uses: "actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b"
49
+ uses: "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065"
50
50
  with:
51
51
  python-version: "${{ matrix.python-version }}"
52
52
  allow-prereleases: true
@@ -60,7 +60,7 @@ jobs:
60
60
  nox --session test -- partial-coverage
61
61
 
62
62
  - name: "Save coverage artifact"
63
- uses: "actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b"
63
+ uses: "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"
64
64
  with:
65
65
  name: "coverage-artifact-${{ matrix.os}}-${{ matrix.python-version}}"
66
66
  path: ".coverage.*"
@@ -76,7 +76,7 @@ jobs:
76
76
  uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
77
77
 
78
78
  - name: "Set up Python"
79
- uses: "actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b"
79
+ uses: "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065"
80
80
  with:
81
81
  python-version: "${{ needs.settings.outputs.default-python-version }}"
82
82
 
@@ -85,7 +85,7 @@ jobs:
85
85
  python -m pip install --upgrade nox
86
86
 
87
87
  - name: "Download coverage artifacts"
88
- uses: "actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16"
88
+ uses: "actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093"
89
89
  with:
90
90
  pattern: "coverage-artifact-*"
91
91
  merge-multiple: true
@@ -106,7 +106,7 @@ jobs:
106
106
  uses: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
107
107
 
108
108
  - name: "Set up Python"
109
- uses: "actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b"
109
+ uses: "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065"
110
110
  with:
111
111
  python-version: "${{ needs.settings.outputs.default-python-version }}"
112
112
 
@@ -0,0 +1,11 @@
1
+ ci:
2
+ autoupdate_schedule: monthly
3
+ exclude: ^.+\.ini$
4
+ repos:
5
+ # Batch of helpful formatters and patterns
6
+ - repo: https://github.com/pre-commit/pre-commit-hooks
7
+ rev: "v5.0.0"
8
+ hooks:
9
+ - id: trailing-whitespace
10
+ - id: end-of-file-fixer
11
+ - id: mixed-line-ending
@@ -109,12 +109,3 @@ nox -s update-deps
109
109
  ```bash
110
110
  nox -s upgrade-deps
111
111
  ```
112
-
113
- ---
114
-
115
- ## [pre-commit](https://pre-commit.com)
116
-
117
- > A framework for managing and maintaining multi-language pre-commit hooks.
118
-
119
- This repo is setup with a `.pre-commit-config.yaml` with the expectation that
120
- any code submitted for review already passes all selected pre-commit checks.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commented-configparser
3
- Version: 2.2.0
4
- Summary: Custom ConfigParser class that preserves comments in file when writing
3
+ Version: 3.0.0
4
+ Summary: A custom ConfigParser class that preserves comments and most formatting when writing loaded config out.
5
5
  Author-email: Preocts <preocts@preocts.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: homepage, https://github.com/Preocts/commented-configparser
@@ -38,9 +38,12 @@ Dynamic: license-file
38
38
 
39
39
  ---
40
40
 
41
- A custom ConfigParser class that preserves comments and option casing when writing loaded config out.
41
+ A custom ConfigParser class that preserves comments and most formatting when
42
+ writing loaded config out.
42
43
 
43
- This library gives you a custom class of the standard library's `configparser.ConfigParger` which will preserve the comments of a loaded config file when writing that file back out.
44
+ This library gives you a custom class of the standard library's
45
+ `configparser.ConfigParger` which will preserve the comments of a loaded config
46
+ file when writing that file back out.
44
47
 
45
48
  ---
46
49
 
@@ -84,7 +87,11 @@ with open("myconfig.ini", "w") as savefile:
84
87
 
85
88
  ## Results
86
89
 
87
- We favor the line spacing choices of the `ConfigParser` class so the input format may not be preserved completely. However, the comments will be preserved.
90
+ There is the attempt to retain the original format. Known formats that will not
91
+ be preserved:
92
+
93
+ 1. Indents will not be preserved outside of multi-line values
94
+ 2. Spacing around assignments will be normalized.
88
95
 
89
96
  ### Before
90
97
 
@@ -103,13 +110,12 @@ logging=true
103
110
  ; and many could be between things
104
111
  [NEW SECTION]
105
112
  # Another comment
106
- multi-line=
113
+ multiLine=
107
114
  value01
108
115
  value02
109
116
  value03
110
117
  closing=0
111
118
  # Trailing comment
112
-
113
119
  ```
114
120
 
115
121
  ### After
@@ -123,16 +129,16 @@ foo = bar
123
129
  trace = false
124
130
  logging = true
125
131
  ; This is a comment as well
132
+
126
133
  # so we need to track all of them
127
- ; and many could be between things
128
134
 
135
+ ; and many could be between things
129
136
  [NEW SECTION]
130
137
  # Another comment
131
- multi-line =
138
+ multiLine =
132
139
  value01
133
140
  value02
134
141
  value03
135
142
  closing = 0
136
143
  # Trailing comment
137
-
138
144
  ```
@@ -12,9 +12,12 @@
12
12
 
13
13
  ---
14
14
 
15
- A custom ConfigParser class that preserves comments and option casing when writing loaded config out.
15
+ A custom ConfigParser class that preserves comments and most formatting when
16
+ writing loaded config out.
16
17
 
17
- This library gives you a custom class of the standard library's `configparser.ConfigParger` which will preserve the comments of a loaded config file when writing that file back out.
18
+ This library gives you a custom class of the standard library's
19
+ `configparser.ConfigParger` which will preserve the comments of a loaded config
20
+ file when writing that file back out.
18
21
 
19
22
  ---
20
23
 
@@ -58,7 +61,11 @@ with open("myconfig.ini", "w") as savefile:
58
61
 
59
62
  ## Results
60
63
 
61
- We favor the line spacing choices of the `ConfigParser` class so the input format may not be preserved completely. However, the comments will be preserved.
64
+ There is the attempt to retain the original format. Known formats that will not
65
+ be preserved:
66
+
67
+ 1. Indents will not be preserved outside of multi-line values
68
+ 2. Spacing around assignments will be normalized.
62
69
 
63
70
  ### Before
64
71
 
@@ -77,13 +84,12 @@ logging=true
77
84
  ; and many could be between things
78
85
  [NEW SECTION]
79
86
  # Another comment
80
- multi-line=
87
+ multiLine=
81
88
  value01
82
89
  value02
83
90
  value03
84
91
  closing=0
85
92
  # Trailing comment
86
-
87
93
  ```
88
94
 
89
95
  ### After
@@ -97,16 +103,16 @@ foo = bar
97
103
  trace = false
98
104
  logging = true
99
105
  ; This is a comment as well
106
+
100
107
  # so we need to track all of them
101
- ; and many could be between things
102
108
 
109
+ ; and many could be between things
103
110
  [NEW SECTION]
104
111
  # Another comment
105
- multi-line =
112
+ multiLine =
106
113
  value01
107
114
  value02
108
115
  value03
109
116
  closing = 0
110
117
  # Trailing comment
111
-
112
118
  ```
@@ -16,6 +16,24 @@ VENV_PATH = "./.venv"
16
16
  LINT_PATH = "./src"
17
17
  REQUIREMENTS_PATH = "./requirements"
18
18
 
19
+ LINTING_COMMANDS = (
20
+ (
21
+ "isort",
22
+ "--verbose",
23
+ "--force-single-line-imports",
24
+ "--profile",
25
+ "black",
26
+ "--add-import",
27
+ "from __future__ import annotations",
28
+ LINT_PATH,
29
+ TESTS_PATH,
30
+ ),
31
+ ("black", "--verbose", LINT_PATH, TESTS_PATH),
32
+ ("flake8", "--show-source", "--verbose", LINT_PATH, TESTS_PATH),
33
+ ("mypy", "--no-incremental", "--package", MODULE_NAME),
34
+ ("mypy", "--no-incremental", TESTS_PATH),
35
+ )
36
+
19
37
  # What we allowed to clean (delete)
20
38
  CLEANABLE_TARGETS = [
21
39
  "./dist",
@@ -55,13 +73,7 @@ def dev(session: nox.Session) -> None:
55
73
  python = partial(session.run, f"{venv_path}/python", "-m")
56
74
  contraint = ("--constraint", f"{REQUIREMENTS_PATH}/constraints.txt")
57
75
 
58
- for requirement_file in get_requirement_files():
59
- python("pip", "install", "-r", requirement_file, *contraint, external=True)
60
-
61
- python("pip", "install", "--editable", ".", *contraint, external=True)
62
-
63
- python("pip", "install", "pre-commit", external=True)
64
- session.run(f"{venv_path}/pre-commit", "install", external=True)
76
+ python("pip", "install", "--editable", ".[dev,test]", *contraint, external=True)
65
77
 
66
78
  if not os.environ.get("VIRTUAL_ENV"):
67
79
  session.log(f"\n\nRun '{activate_command}' to enter the virtual environment.\n")
@@ -109,26 +121,12 @@ def run_linters_and_formatters(session: nox.Session) -> None:
109
121
  print_standard_logs(session)
110
122
 
111
123
  contraint = ("--constraint", f"{REQUIREMENTS_PATH}/constraints.txt")
112
- session.install(".[dev]", *contraint)
124
+ session.install(".[dev,test]", *contraint)
113
125
 
114
126
  python = partial(session.run, "python", "-m")
115
127
 
116
- # Handle anything tool that applies corrections first.
117
- python(
118
- "isort",
119
- "--verbose",
120
- "--force-single-line-imports",
121
- "--profile",
122
- "black",
123
- "--add-import",
124
- "from __future__ import annotations",
125
- LINT_PATH,
126
- )
127
- python("black", "--verbose", LINT_PATH)
128
-
129
- # Linters: aka, things that yell but want you to fix things
130
- python("flake8", "--show-source", "--verbose", LINT_PATH)
131
- python("mypy", "--no-incremental", "--package", MODULE_NAME)
128
+ for linter_command in LINTING_COMMANDS:
129
+ python(*linter_command)
132
130
 
133
131
 
134
132
  @nox.session()
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "commented-configparser"
7
7
  requires-python = ">=3.9"
8
- description = "Custom ConfigParser class that preserves comments in file when writing"
8
+ description = "A custom ConfigParser class that preserves comments and most formatting when writing loaded config out."
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  authors = [
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commented-configparser
3
- Version: 2.2.0
4
- Summary: Custom ConfigParser class that preserves comments in file when writing
3
+ Version: 3.0.0
4
+ Summary: A custom ConfigParser class that preserves comments and most formatting when writing loaded config out.
5
5
  Author-email: Preocts <preocts@preocts.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: homepage, https://github.com/Preocts/commented-configparser
@@ -38,9 +38,12 @@ Dynamic: license-file
38
38
 
39
39
  ---
40
40
 
41
- A custom ConfigParser class that preserves comments and option casing when writing loaded config out.
41
+ A custom ConfigParser class that preserves comments and most formatting when
42
+ writing loaded config out.
42
43
 
43
- This library gives you a custom class of the standard library's `configparser.ConfigParger` which will preserve the comments of a loaded config file when writing that file back out.
44
+ This library gives you a custom class of the standard library's
45
+ `configparser.ConfigParger` which will preserve the comments of a loaded config
46
+ file when writing that file back out.
44
47
 
45
48
  ---
46
49
 
@@ -84,7 +87,11 @@ with open("myconfig.ini", "w") as savefile:
84
87
 
85
88
  ## Results
86
89
 
87
- We favor the line spacing choices of the `ConfigParser` class so the input format may not be preserved completely. However, the comments will be preserved.
90
+ There is the attempt to retain the original format. Known formats that will not
91
+ be preserved:
92
+
93
+ 1. Indents will not be preserved outside of multi-line values
94
+ 2. Spacing around assignments will be normalized.
88
95
 
89
96
  ### Before
90
97
 
@@ -103,13 +110,12 @@ logging=true
103
110
  ; and many could be between things
104
111
  [NEW SECTION]
105
112
  # Another comment
106
- multi-line=
113
+ multiLine=
107
114
  value01
108
115
  value02
109
116
  value03
110
117
  closing=0
111
118
  # Trailing comment
112
-
113
119
  ```
114
120
 
115
121
  ### After
@@ -123,16 +129,16 @@ foo = bar
123
129
  trace = false
124
130
  logging = true
125
131
  ; This is a comment as well
132
+
126
133
  # so we need to track all of them
127
- ; and many could be between things
128
134
 
135
+ ; and many could be between things
129
136
  [NEW SECTION]
130
137
  # Another comment
131
- multi-line =
138
+ multiLine =
132
139
  value01
133
140
  value02
134
141
  value03
135
142
  closing = 0
136
143
  # Trailing comment
137
-
138
144
  ```
@@ -7,6 +7,7 @@ README.md
7
7
  noxfile.py
8
8
  pyproject.toml
9
9
  .github/workflows/pypi-publish.yml
10
+ .github/workflows/pypi-test-publish.yml
10
11
  .github/workflows/python-tests.yml
11
12
  requirements/constraints.txt
12
13
  requirements/requirements-dev.txt
@@ -21,6 +22,8 @@ src/commentedconfigparser/__init__.py
21
22
  src/commentedconfigparser/commentedconfigparser.py
22
23
  src/commentedconfigparser/py.typed
23
24
  tests/commentedconfigparser_test.py
25
+ tests/empty_comments_expected.ini
26
+ tests/empty_comments_input.ini
24
27
  tests/header_expected.ini
25
28
  tests/header_input.ini
26
29
  tests/multi01_input.ini
@@ -16,10 +16,11 @@ if TYPE_CHECKING:
16
16
 
17
17
  __all__ = ["CommentedConfigParser"]
18
18
 
19
- COMMENT_PATTERN = re.compile(r"^\s*[#|;]\s*(.+)$")
20
- COMMENT_OPTION_PATTERN = re.compile(r"^(\s*)?__comment_\d+\s?[=|:]\s?(.*)$")
21
- KEY_PATTERN = re.compile(r"^(.+?)\s?[=|:].*$")
22
- SECTION_PATTERN = re.compile(r"^\s*\[(.+)\]\s*$")
19
+ _COMMENT_PATTERN = re.compile(r"^\s*[#|;]\s*(.*)$")
20
+ _BLANK_LINE_PATTERN = re.compile(r"^\s*$")
21
+ _COMMENT_OPTION_PATTERN = re.compile(r"^(\s*)?__comment_\d+\s?[=|:]\s?(.*)$")
22
+ _KEY_PATTERN = re.compile(r"^(.+?)\s?[=|:].*$")
23
+ _SECTION_PATTERN = re.compile(r"^\s*\[(.+)\]\s*$")
23
24
 
24
25
 
25
26
  class CommentedConfigParser(ConfigParser):
@@ -57,7 +58,15 @@ class CommentedConfigParser(ConfigParser):
57
58
 
58
59
  def read_file(self, f: Iterable[str], source: str | None = None) -> None:
59
60
  content = self._translate_comments([line for line in f])
60
- return super().read_file(content.splitlines(), source)
61
+ self.__is_reading = True
62
+ super().read_file(content.splitlines(), source)
63
+ self.__is_reading = False
64
+
65
+ def optionxform(self, optionstr: str) -> str:
66
+ if hasattr(self, "_CommentedConfigParser__is_reading") and self.__is_reading:
67
+ return optionstr
68
+
69
+ return super().optionxform(optionstr)
61
70
 
62
71
  def write(
63
72
  self,
@@ -76,29 +85,34 @@ class CommentedConfigParser(ConfigParser):
76
85
 
77
86
  # To save the pain of mirroring ConfigParser's __init__ these two
78
87
  # attributes are created in the instance here, when needed.
79
- if not hasattr(self, "_headers"):
80
- self._headers: list[str] = []
88
+ if not hasattr(self, "_CommentedConfigParser__header_block"):
89
+ self.__header_block: list[str] = []
81
90
 
82
- if not hasattr(self, "_commentprefix"):
83
- self._commentprefix = 0
91
+ if not hasattr(self, "_CommentedConfigParser__file_index"):
92
+ self.__file_index = 0
84
93
 
85
94
  translated_lines = []
86
95
  for idx, line in enumerate(content):
87
- if SECTION_PATTERN.match(line):
96
+ if _SECTION_PATTERN.match(line):
88
97
  seen_section = True
89
98
 
90
99
  if not seen_section:
91
100
  # Assume lines before a section are comments. If they are not
92
101
  # the parent class will raise the needed exceptions for an
93
102
  # invalid config format.
94
- self._headers.append(line)
103
+ self.__header_block.append(line)
95
104
 
96
- elif COMMENT_PATTERN.match(line):
105
+ elif _COMMENT_PATTERN.match(line):
97
106
  # Translate the comment into an option for the section. These
98
107
  # are handled by the parent and retain order of insertion.
99
- line = f"__comment_{self._commentprefix}{idx}={line.lstrip()}"
108
+ line = f"__comment_{self.__file_index}{idx}={line.lstrip()}"
109
+
110
+ elif _BLANK_LINE_PATTERN.match(line):
111
+ # Blank lines cannot be stripped or the newline char will
112
+ # be lost which causes issues downstream.
113
+ line = f"__comment_{self.__file_index}{idx}={line}"
100
114
 
101
- elif KEY_PATTERN.match(line) or SECTION_PATTERN.match(line):
115
+ elif _KEY_PATTERN.match(line) or _SECTION_PATTERN.match(line):
102
116
  # Strip the left whitespace from sections and keys. This will
103
117
  # leave only multiline values with leading whitespace preventing
104
118
  # the saved output from incorrectly indenting after a comment
@@ -109,7 +123,7 @@ class CommentedConfigParser(ConfigParser):
109
123
 
110
124
  # If additional configuration files are loaded, comments may end up sharing
111
125
  # idx values which will clobber previously loaded comments.
112
- self._commentprefix += 1
126
+ self.__file_index += 1
113
127
 
114
128
  return "".join(translated_lines)
115
129
 
@@ -117,11 +131,15 @@ class CommentedConfigParser(ConfigParser):
117
131
  """Restore comment options to comments."""
118
132
  # Apply the headers before parsing the config lines
119
133
  rendered = []
120
- if hasattr(self, "_headers"):
121
- rendered += self._headers
134
+ if hasattr(self, "_CommentedConfigParser__header_block"):
135
+ rendered += self.__header_block
122
136
 
123
137
  for line in content.splitlines():
124
- comment_match = COMMENT_OPTION_PATTERN.match(line)
138
+ if not line:
139
+ # Skip the provided empty lines and use our own instead
140
+ continue
141
+
142
+ comment_match = _COMMENT_OPTION_PATTERN.match(line)
125
143
  if comment_match:
126
144
  line = comment_match.group(2)
127
145
 
@@ -20,10 +20,12 @@ from commentedconfigparser.commentedconfigparser import CommentedConfigParser
20
20
  (" \tThis is a comment", False),
21
21
  ("", False),
22
22
  (" \t", False),
23
+ ("#", True),
24
+ ("#\t", True),
23
25
  ),
24
26
  )
25
27
  def test_comment_pattern(line: str, expected: bool) -> None:
26
- result = commentedconfigparser.COMMENT_PATTERN.match(line)
28
+ result = commentedconfigparser._COMMENT_PATTERN.match(line)
27
29
 
28
30
  assert bool(result) is expected
29
31
 
@@ -40,7 +42,7 @@ def test_comment_pattern(line: str, expected: bool) -> None:
40
42
  ),
41
43
  )
42
44
  def test_section_pattern(line: str, expected: bool) -> None:
43
- result = commentedconfigparser.SECTION_PATTERN.match(line)
45
+ result = commentedconfigparser._SECTION_PATTERN.match(line)
44
46
 
45
47
  assert bool(result) is expected
46
48
 
@@ -61,7 +63,7 @@ def test_section_pattern(line: str, expected: bool) -> None:
61
63
  )
62
64
  def test_key_pattern(line: str, expected: str) -> None:
63
65
 
64
- match = commentedconfigparser.KEY_PATTERN.match(line)
66
+ match = commentedconfigparser._KEY_PATTERN.match(line)
65
67
 
66
68
  assert match
67
69
  assert match.group(1) == expected
@@ -77,7 +79,7 @@ def test_key_pattern(line: str, expected: str) -> None:
77
79
  )
78
80
  def test_comment_option_pattern(line: str, expected: str) -> None:
79
81
 
80
- match = commentedconfigparser.COMMENT_OPTION_PATTERN.match(line)
82
+ match = commentedconfigparser._COMMENT_OPTION_PATTERN.match(line)
81
83
 
82
84
  assert match
83
85
  assert match.group(2) == expected
@@ -126,7 +128,7 @@ def test_regression_read_dict_loads_normally() -> None:
126
128
 
127
129
  def test_regression_write_normally() -> None:
128
130
  cc = CommentedConfigParser()
129
- expected = "[TEST]\ntest=pass\n\n"
131
+ expected = "[TEST]\ntest=pass\n"
130
132
  cc.read_string(expected)
131
133
  mock_file = StringIO()
132
134
 
@@ -137,7 +139,7 @@ def test_regression_write_normally() -> None:
137
139
 
138
140
  def test_write_with_no_comments() -> None:
139
141
  cc = CommentedConfigParser()
140
- expected = "[TEST]\ntest=pass\n\n"
142
+ expected = "[TEST]\ntest=pass\n"
141
143
  cc.read_dict({"TEST": {"test": "pass"}})
142
144
  mock_file = StringIO()
143
145
 
@@ -149,6 +151,7 @@ def test_write_with_no_comments() -> None:
149
151
  @pytest.mark.parametrize(
150
152
  ("ini_in", "ini_expected"),
151
153
  (
154
+ (["empty_comments_input.ini"], "empty_comments_expected.ini"),
152
155
  (["header_input.ini"], "header_expected.ini"),
153
156
  (["pydocs_input.ini"], "pydocs_expected.ini"),
154
157
  (["regression_original_input.ini"], "regression_original_expected.ini"),
@@ -178,7 +181,7 @@ def test_issue_46_duplicating_sections(tmp_path: Path) -> None:
178
181
  # https://github.com/Preocts/commented-configparser/issues/46
179
182
  tmp_file = tmp_path / "issue46_test_file.ini"
180
183
  starting_config = "[example]\nfoo = 0\n"
181
- expected = "[example]\nfoo = 9\n\n"
184
+ expected = "[example]\nfoo = 9\n"
182
185
  tmp_file.write_text(starting_config, "utf-8")
183
186
  cc = CommentedConfigParser()
184
187
  cc.read(tmp_file)
@@ -0,0 +1,17 @@
1
+ # This has some headers
2
+ #
3
+ # These are comments that happen before any sections are defined
4
+
5
+ [DEFAULT]
6
+ foo = bar
7
+
8
+ # Comment here
9
+ #
10
+ bar = foo
11
+ ;
12
+ ; Comment here too
13
+
14
+
15
+ [BIZ]
16
+ baz = bar
17
+
@@ -0,0 +1,17 @@
1
+ # This has some headers
2
+ #
3
+ # These are comments that happen before any sections are defined
4
+
5
+ [DEFAULT]
6
+ foo = bar
7
+
8
+ # Comment here
9
+ #
10
+ bar = foo
11
+ ;
12
+ ; Comment here too
13
+
14
+
15
+ [BIZ]
16
+ baz=bar
17
+
@@ -8,4 +8,3 @@
8
8
  foo = bar
9
9
  # Comment here
10
10
  bar = foo
11
-
@@ -8,4 +8,3 @@ foo = bar
8
8
  unique to multi02 = foo
9
9
  ; A nice little comment here from multi01
10
10
  unique to multi01 = foo
11
-
@@ -12,7 +12,7 @@ values like this = 1000000
12
12
  or this = 3.14159265359
13
13
  are they treated as numbers? = no
14
14
  integers, floats and booleans are held as = strings
15
- can use the api to get converted values directly = true
15
+ can use the API to get converted values directly = true
16
16
 
17
17
  [Multiline Values]
18
18
  chorus = I'm a lumberjack, and I'm okay
@@ -25,6 +25,7 @@ empty string value here =
25
25
  [You can use comments]
26
26
  # like this
27
27
  ; or this
28
+
28
29
  # By default only in an empty line.
29
30
  # Inline comments can be harmful because they prevent users
30
31
  # from using the delimiting characters as parts of values.
@@ -40,4 +41,3 @@ multiline_values = are
40
41
  deeper than the first line
41
42
  of a value
42
43
  # Did I mention we can indent comments, too?
43
-
@@ -6,9 +6,10 @@ foo = bar
6
6
  trace = false
7
7
  logging = true
8
8
  ; This is a comment as well
9
+
9
10
  # so we need to track all of them
10
- ; and many could be between things
11
11
 
12
+ ; and many could be between things
12
13
  [NEW SECTION]
13
14
  foo = bar
14
15
  # Unique foo
@@ -17,5 +18,5 @@ multi-line =
17
18
  value02
18
19
  value03
19
20
  closing = 0
21
+ caseVariant = 0
20
22
  # Trailing comment
21
-
@@ -18,4 +18,5 @@ multi-line=
18
18
  value02
19
19
  value03
20
20
  closing=0
21
+ caseVariant=0
21
22
  # Trailing comment
@@ -1,32 +0,0 @@
1
- ci:
2
- autoupdate_schedule: monthly
3
- exclude: ^.+\.ini$
4
- repos:
5
- # Batch of helpful formatters and patterns
6
- - repo: https://github.com/pre-commit/pre-commit-hooks
7
- rev: "v5.0.0"
8
- hooks:
9
- - id: check-json
10
- - id: check-toml
11
- - id: check-yaml
12
- - id: trailing-whitespace
13
- - id: end-of-file-fixer
14
- - id: check-docstring-first
15
-
16
- # Adds a standard feel to import segments
17
- - repo: https://github.com/pycqa/isort
18
- rev: 6.0.1
19
- hooks:
20
- - id: isort
21
- args:
22
- - "--force-single-line-imports"
23
- - "--add-import"
24
- - "from __future__ import annotations"
25
- - "--profile"
26
- - "black"
27
-
28
- # Format code. No, I don't like everything black does either.
29
- - repo: https://github.com/psf/black-pre-commit-mirror
30
- rev: 25.1.0
31
- hooks:
32
- - id: black