iiconf 0.0.2__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.
iiconf-0.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lqxnjk
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.
iiconf-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.5
2
+ Name: iiconf
3
+ Version: 0.0.2
4
+ Summary: Parse INI and .env text into plain dictionaries.
5
+ Project-URL: Homepage, https://github.com/lqxnjk/iiconf
6
+ Author: lqxnjk
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: config,dotenv,env,ini,settings
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ # iiconf
22
+
23
+ Read INI and `.env` files into plain dicts — no classes, no global state, no
24
+ environment mutation.
25
+
26
+ ## Install
27
+
28
+ ```console
29
+ pip install iiconf
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```python
35
+ from iiconf import load_config, parse_env, parse_ini
36
+
37
+ parse_env("""
38
+ # local settings
39
+ export HOST=localhost
40
+ PORT=5432
41
+ MOTTO="hello # not a comment"
42
+ """)
43
+ # {'HOST': 'localhost', 'PORT': '5432', 'MOTTO': 'hello # not a comment'}
44
+
45
+ parse_ini("""
46
+ key = before-section
47
+ [db]
48
+ host = localhost
49
+ port = 5432
50
+ """)
51
+ # {'': {'key': 'before-section'}, 'db': {'host': 'localhost', 'port': '5432'}}
52
+
53
+ load_config("settings.ini")
54
+ ```
55
+
56
+ ## Behaviour
57
+
58
+ | Aspect | Detail |
59
+ | --- | --- |
60
+ | Comments | `#` to end of line, except inside quotes |
61
+ | Quotes | surrounding `'` or `"` stripped |
62
+ | Separator | `=` or `:`, first occurrence only |
63
+ | Substitution | **not** performed, `${X}` stays literal |
64
+ | Unparseable line | `ValueError` |
65
+
66
+ Values stay strings on purpose: guessing whether `"0123"` is a number or an ID
67
+ is how leading zeros get lost.
68
+
69
+ ## License
70
+
71
+ MIT
iiconf-0.0.2/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # iiconf
2
+
3
+ Read INI and `.env` files into plain dicts — no classes, no global state, no
4
+ environment mutation.
5
+
6
+ ## Install
7
+
8
+ ```console
9
+ pip install iiconf
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```python
15
+ from iiconf import load_config, parse_env, parse_ini
16
+
17
+ parse_env("""
18
+ # local settings
19
+ export HOST=localhost
20
+ PORT=5432
21
+ MOTTO="hello # not a comment"
22
+ """)
23
+ # {'HOST': 'localhost', 'PORT': '5432', 'MOTTO': 'hello # not a comment'}
24
+
25
+ parse_ini("""
26
+ key = before-section
27
+ [db]
28
+ host = localhost
29
+ port = 5432
30
+ """)
31
+ # {'': {'key': 'before-section'}, 'db': {'host': 'localhost', 'port': '5432'}}
32
+
33
+ load_config("settings.ini")
34
+ ```
35
+
36
+ ## Behaviour
37
+
38
+ | Aspect | Detail |
39
+ | --- | --- |
40
+ | Comments | `#` to end of line, except inside quotes |
41
+ | Quotes | surrounding `'` or `"` stripped |
42
+ | Separator | `=` or `:`, first occurrence only |
43
+ | Substitution | **not** performed, `${X}` stays literal |
44
+ | Unparseable line | `ValueError` |
45
+
46
+ Values stay strings on purpose: guessing whether `"0123"` is a number or an ID
47
+ is how leading zeros get lost.
48
+
49
+ ## License
50
+
51
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "iiconf"
7
+ version = "0.0.2"
8
+ description = "Parse INI and .env text into plain dictionaries."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "lqxnjk" }]
13
+ keywords = ["config", "ini", "dotenv", "env", "settings"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = []
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/lqxnjk/iiconf"
28
+
29
+ [tool.pytest.ini_options]
30
+ pythonpath = ["src"]
31
+ testpaths = ["tests"]
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/iiconf"]
35
+
36
+ [tool.hatch.build.targets.sdist]
37
+ include = ["src/iiconf", "tests", "README.md", "LICENSE"]
@@ -0,0 +1,101 @@
1
+ """
2
+ Author: lqxnjk <lqxnjk@qq.com>
3
+ Created: 2026-10-23 10:25
4
+ Modified: 2026-10-23 10:25
5
+ Python Version: 3.10
6
+ Minimal INI and .env parsing.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from pathlib import Path
13
+
14
+ __all__ = ["parse_env", "parse_ini"]
15
+
16
+ _SECTION_RE = re.compile(r"^\s*\[(?P<name>[^\]]+)\]\s*$")
17
+ _PAIR_RE = re.compile(r"^\s*(?P<key>[^=:\s]+)\s*[=:]\s*(?P<value>.*?)\s*$")
18
+ _QUOTES = ("'", '"')
19
+
20
+
21
+ def _strip_comment(m_line: str) -> str:
22
+ """Cut an inline ``#`` comment, ignoring hashes inside quotes."""
23
+ quote: str | None = None
24
+ for index, char in enumerate(m_line):
25
+ if quote:
26
+ if char == quote:
27
+ quote = None
28
+ elif char in _QUOTES:
29
+ quote = char
30
+ elif char == "#":
31
+ return m_line[:index]
32
+ return m_line
33
+
34
+
35
+ def _unquote(m_value: str) -> str:
36
+ if len(m_value) >= 2 and m_value[0] == m_value[-1] and m_value[0] in _QUOTES:
37
+ return m_value[1:-1]
38
+ return m_value
39
+
40
+
41
+ def parse_env(m_text: str) -> dict[str, str]:
42
+ """Parse dotenv text into ``{key: value}``.
43
+
44
+ Blank lines and ``#`` comments are skipped, an optional ``export`` prefix is
45
+ removed, and surrounding quotes are stripped. Values are never evaluated —
46
+ ``${OTHER}`` stays literal, because a config parser that executes
47
+ substitutions is a config parser that surprises you.
48
+ """
49
+ result: dict[str, str] = {}
50
+ for line in m_text.splitlines():
51
+ stripped = _strip_comment(line).strip()
52
+ if not stripped:
53
+ continue
54
+ if stripped.lower().startswith("export "):
55
+ stripped = stripped[7:].strip()
56
+ match = _PAIR_RE.match(stripped)
57
+ if not match:
58
+ raise ValueError(f"cannot parse line: {line!r}")
59
+ result[match.group("key")] = _unquote(match.group("value"))
60
+ return result
61
+
62
+
63
+ def parse_ini(m_text: str) -> dict[str, dict[str, str]]:
64
+ """Parse INI text into ``{section: {key: value}}``.
65
+
66
+ Keys before the first section land under ``""``. Values keep ``=`` and ``:``
67
+ characters after the first separator; quotes are stripped.
68
+ """
69
+ result: dict[str, dict[str, str]] = {}
70
+ current = ""
71
+ for line in m_text.splitlines():
72
+ stripped = _strip_comment(line).strip()
73
+ if not stripped:
74
+ continue
75
+ section = _SECTION_RE.match(stripped)
76
+ if section:
77
+ current = section.group("name").strip()
78
+ result.setdefault(current, {})
79
+ continue
80
+ match = _PAIR_RE.match(stripped)
81
+ if not match:
82
+ raise ValueError(f"cannot parse line: {line!r}")
83
+ result.setdefault(current, {})[match.group("key")] = _unquote(match.group("value"))
84
+ return result
85
+
86
+
87
+ def load_config(m_path: str | Path) -> dict[str, object]:
88
+ """Parse a file, choosing the format from its suffix.
89
+
90
+ ``.ini`` / ``.cfg`` use :func:`parse_ini`; everything else is treated as
91
+ dotenv. An unsupported suffix raises ``ValueError`` so a typo in the path
92
+ cannot be mistaken for an empty config.
93
+ """
94
+ path = Path(m_path)
95
+ text = path.read_text(encoding="utf-8")
96
+ suffix = path.suffix.lower()
97
+ if suffix in (".ini", ".cfg"):
98
+ return parse_ini(text)
99
+ if suffix in (".env", "", ".txt"):
100
+ return parse_env(text)
101
+ raise ValueError(f"unsupported config suffix: {suffix!r}")
File without changes
@@ -0,0 +1,53 @@
1
+ """
2
+ Author: lqxnjk <lqxnjk@qq.com>
3
+ Created: 2026-10-23 10:25
4
+ Modified: 2026-10-23 10:25
5
+ Python Version: 3.10
6
+ """
7
+
8
+ import pytest
9
+
10
+ from iiconf import load_config, parse_env, parse_ini
11
+
12
+
13
+ def test_env_basic() -> None:
14
+ assert parse_env("A=1\nB = two\n") == {"A": "1", "B": "two"}
15
+
16
+
17
+ def test_env_export_and_quotes() -> None:
18
+ assert parse_env("export A=1\nB='x y'\nC=\"z\"\n") == {"A": "1", "B": "x y", "C": "z"}
19
+
20
+
21
+ def test_env_comments_and_blanks() -> None:
22
+ assert parse_env("# c\n\nA=1 # tail\nB=\"keep # this\"\n") == {"A": "1", "B": "keep # this"}
23
+
24
+
25
+ def test_ini_sections() -> None:
26
+ text = "key = v0\n[db]\nhost = localhost\nport: 5432\n"
27
+ assert parse_ini(text) == {"": {"key": "v0"}, "db": {"host": "localhost", "port": "5432"}}
28
+
29
+
30
+ def test_no_substitution() -> None:
31
+ assert parse_env("A=${B}\n") == {"A": "${B}"}
32
+
33
+
34
+ def test_unparseable_line() -> None:
35
+ with pytest.raises(ValueError):
36
+ parse_env("this is not a pair\n")
37
+
38
+
39
+ def test_load_config_dispatch(tmp_path) -> None:
40
+ ini = tmp_path / "a.ini"
41
+ ini.write_text("[s]\nk = 1\n", encoding="utf-8")
42
+ assert load_config(ini) == {"s": {"k": "1"}}
43
+
44
+ env = tmp_path / "a.env"
45
+ env.write_text("K=1\n", encoding="utf-8")
46
+ assert load_config(env) == {"K": "1"}
47
+
48
+
49
+ def test_unsupported_suffix(tmp_path) -> None:
50
+ target = tmp_path / "a.yaml"
51
+ target.write_text("k: 1\n", encoding="utf-8")
52
+ with pytest.raises(ValueError):
53
+ load_config(target)