Skip to content

Commit 4d20dcb

Browse files
committed
add(configuration): ability to store configuration in [tool:qgis-plugin-c] section in plugin's metadata.txt
1 parent 430a98e commit 4d20dcb

4 files changed

Lines changed: 188 additions & 0 deletions

File tree

docs/configuration/options.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The plugin must have a configuration, located at the top directory; it can be ei
77
- a YAML file named `.qgis-plugin-ci`
88
- an INI file named `setup.cfg` with a `[qgis-plugin-ci]` section
99
- a TOML file (= your actual `pyproject.toml` file) with a `[tool.qgis-plugin-ci]` section.
10+
- a section `[tool:qgis-plugin-ci]` in the plugin's metadata.txt, but `plugin_path` at least has to defined in one of the above configuration files
1011

1112
In the configuration, you should at least provide the following configuration:
1213

@@ -65,3 +66,36 @@ plugin_path = "qgis_plugin_ci_testing"
6566
github_organization_slug = "opengisch"
6667
project_slug = "qgis-plugin-ci"
6768
```
69+
70+
### Combining minimal configuration with `metadata.txt`
71+
72+
When the main configuration file only sets `plugin_path` (and optionally `changelog_path`), qgis-plugin-ci will look for a `[tool:qgis-plugin-ci]` section in `{plugin_path}/metadata.txt` and merge any options found there. Values in the main config file always take precedence.
73+
74+
This allows reducing the main config file to a single line:
75+
76+
```yaml
77+
# .qgis-plugin-ci
78+
plugin_path: qtribu
79+
```
80+
81+
With the remaining options in `qtribu/metadata.txt`:
82+
83+
```ini
84+
[general]
85+
name=QTribu
86+
[...]
87+
88+
[tool:qgis-plugin-ci]
89+
create_date = 2021-03-02
90+
github_organization_slug=geotribu
91+
project_slug=qtribu
92+
repository_url_raw = https://raw.githubusercontent.com/geotribu/qtribu/
93+
repository_plugin_id = 2733
94+
timezone=Europe/Paris
95+
```
96+
97+
If `metadata.txt` is missing at the expected path, or if the `[tool:qgis-plugin-ci]` section is missing, a warning is emitted and qgis-plugin-ci continues with the minimal configuration.
98+
99+
:::{note}
100+
The `[tool:qgis-plugin-ci]` section is already used by qgis-plugin-ci at packaging time to write runtime values (`commitNumber`, `commitSha1`, `dateTime`). Static configuration options and runtime-written keys can coexist in the same section.
101+
:::

docs/partials/_extra_plugin_metadata.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ commitSha1=
1717
dateTime=
1818
```
1919

20+
This same section can also hold **static configuration options**
21+
(e.g. `github_organization_slug`, `timezone`) when the main config file contains
22+
only `plugin_path`. See [Minimal configuration with `metadata.txt`](../configuration/options.md#combining-minimal-configuration-with-metadata-txt).
23+
2024
:::

qgispluginci/parameters.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ def make_from(
153153
)
154154

155155
def load_config(path_to_config_file: Path, file_name: str) -> dict[str, Any]:
156+
"""Load configuration from a file.
157+
158+
Args:
159+
path_to_config_file (Path): path to the configuration file.
160+
file_name (str): file name.
161+
162+
Raises:
163+
configuration_not_found: if any proper configuration is found in the file.
164+
165+
Returns:
166+
dict[str, Any]: configuration as dict
167+
"""
156168
if file_name == "setup.cfg":
157169
config = configparser.ConfigParser()
158170
config.read(path_to_config_file)
@@ -197,6 +209,39 @@ def explore_config() -> dict[str, Any]:
197209
config_dict = load_config(path_to_config_file, file_name)
198210
else:
199211
config_dict = explore_config()
212+
213+
# load config from metadata.txt
214+
_extra_config_keys = {
215+
k for k in config_dict if k not in ("plugin_path", "changelog_path")
216+
}
217+
if config_dict.get("plugin_path") and not _extra_config_keys:
218+
_metadata_txt = Path(config_dict["plugin_path"]) / "metadata.txt"
219+
if not _metadata_txt.is_file():
220+
logger.warning(
221+
f"Config only defines 'plugin_path' but {_metadata_txt.resolve()} "
222+
"does not exist. Add a '[tool:qgis-plugin-ci]' section in "
223+
"metadata.txt to configure remaining options."
224+
)
225+
else:
226+
_meta_cfg = configparser.ConfigParser()
227+
_meta_cfg.read(_metadata_txt)
228+
if not _meta_cfg.has_section("tool:qgis-plugin-ci"):
229+
logger.warning(
230+
f"No [tool:qgis-plugin-ci] section found in "
231+
f"{_metadata_txt.resolve()}. "
232+
"Add it to avoid keeping a separate config file."
233+
)
234+
else:
235+
_meta_dict = dict(_meta_cfg.items("tool:qgis-plugin-ci"))
236+
config_dict = {
237+
**_meta_dict,
238+
**config_dict,
239+
}
240+
logger.info(
241+
f"Loaded config from {_metadata_txt.resolve()} "
242+
f"[tool:qgis-plugin-ci]: {_meta_dict}"
243+
)
244+
200245
return cls(config_dict)
201246

202247
def __init__(self, definition: dict[str, Any]):

test/test_parameters.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#! /usr/bin/env python
22

33
# standard
4+
import os
5+
import tempfile
46
import unittest
57
from datetime import datetime
68
from pathlib import Path
@@ -11,6 +13,24 @@
1113

1214

1315
class TestParameters(unittest.TestCase):
16+
# -- helpers ----------------------------------------------------------
17+
18+
@staticmethod
19+
def _minimal_metadata(plugin_dir: Path) -> None:
20+
"""Write a valid metadata.txt with all mandatory fields."""
21+
(plugin_dir / "metadata.txt").write_text(
22+
data="[general]\n"
23+
"name=Quicui\n"
24+
"about=Lorem ipsum GIS ergo sum\n"
25+
"description=Test plugin\n"
26+
"qgisMinimumVersion=3.44.10\n"
27+
"author=Former Esri repentant\n"
28+
"homepage=https://opengisch.github.io/qgis-plugin-ci/\n"
29+
"tracker=https://github.com/opengisch/qgis-plugin-ci/issues\n"
30+
"repository=https://github.com/opengisch/qgis-plugin-ci\n",
31+
encoding="UTF-8",
32+
)
33+
1434
def test_changelog_parameters(self):
1535
"""Test parameters for changelog command."""
1636
# For the changelog command, the configuration file is optional.
@@ -46,3 +66,88 @@ def test_plugin_repo_url_from_config(self):
4666
self.assertEqual(
4767
"https://opengisch.github.io/qgis-plugin-ci/", parameters.plugin_repo_url
4868
)
69+
70+
def test_metadata_txt_ci_section_merged_when_plugin_path_only(self):
71+
"""[tool:qgis-plugin-ci] from metadata.txt is merged when config only defines plugin_path."""
72+
original_dir = Path.cwd()
73+
with tempfile.TemporaryDirectory() as tmp_dir:
74+
try:
75+
os.chdir(tmp_dir)
76+
plugin_dir = Path(tmp_dir) / "my_plugin"
77+
plugin_dir.mkdir()
78+
self._minimal_metadata(plugin_dir)
79+
# Append [tool:qgis-plugin-ci] section
80+
with (plugin_dir / "metadata.txt").open("a") as fh:
81+
fh.write(
82+
"\n[tool:qgis-plugin-ci]\n"
83+
"github_organization_slug=my_org\n"
84+
"project_slug=my_project\n"
85+
"timezone=Europe/Paris\n"
86+
)
87+
config_file = Path(tmp_dir) / ".qgis-plugin-ci"
88+
config_file.write_text("plugin_path: my_plugin\n")
89+
90+
parameters = Parameters.make_from(path_to_config_file=config_file)
91+
92+
self.assertEqual("my_org", parameters.github_organization_slug)
93+
self.assertEqual("my_project", parameters.project_slug)
94+
self.assertEqual("Europe/Paris", parameters.timezone)
95+
finally:
96+
os.chdir(original_dir)
97+
98+
def test_missing_ci_section_in_metadata_txt_logs_warning(self):
99+
"""A warning is logged when metadata.txt exists but has no [tool:qgis-plugin-ci] section."""
100+
original_dir = Path.cwd()
101+
with tempfile.TemporaryDirectory() as tmp_dir:
102+
try:
103+
os.chdir(tmp_dir)
104+
plugin_dir = Path(tmp_dir) / "my_plugin"
105+
plugin_dir.mkdir()
106+
self._minimal_metadata(plugin_dir)
107+
config_file = Path(tmp_dir) / ".qgis-plugin-ci"
108+
config_file.write_text("plugin_path: my_plugin\n")
109+
110+
with self.assertLogs("qgispluginci.parameters", level="WARNING") as cm:
111+
Parameters.make_from(path_to_config_file=config_file)
112+
113+
self.assertTrue(
114+
any("tool:qgis-plugin-ci" in msg for msg in cm.output),
115+
"Expected a warning mentioning [tool:qgis-plugin-ci]",
116+
)
117+
finally:
118+
os.chdir(original_dir)
119+
120+
def test_missing_metadata_txt_logs_warning(self):
121+
"""A warning is logged when metadata.txt itself is absent."""
122+
original_dir = Path.cwd()
123+
with tempfile.TemporaryDirectory() as tmp_dir:
124+
try:
125+
os.chdir(tmp_dir)
126+
config_file = Path(tmp_dir) / ".qgis-plugin-ci"
127+
config_file.write_text("plugin_path: ghost_plugin\n")
128+
129+
with self.assertLogs("qgispluginci.parameters", level="WARNING") as cm:
130+
with self.assertRaises(FileNotFoundError):
131+
Parameters.make_from(path_to_config_file=config_file)
132+
133+
self.assertTrue(
134+
any(
135+
"ghost_plugin" in msg and "metadata.txt" in msg
136+
for msg in cm.output
137+
),
138+
"Expected a warning mentioning the missing metadata.txt path",
139+
)
140+
finally:
141+
os.chdir(original_dir)
142+
143+
def test_metadata_txt_not_checked_when_config_has_extra_keys(self):
144+
"""metadata.txt is not consulted when the config file has more than plugin_path."""
145+
# Fixture has github_organization_slug in addition to plugin_path → new block must NOT trigger.
146+
# If it were triggered and merged a metadata.txt section with conflicting values,
147+
# the assertion below would still pass (config file takes priority), but any warning
148+
# log would betray the wrong code path.
149+
with self.assertNoLogs("qgispluginci.parameters", level="WARNING"):
150+
parameters = Parameters.make_from(
151+
path_to_config_file=Path("test/fixtures/.qgis-plugin-ci")
152+
)
153+
self.assertEqual("opengisch", parameters.github_organization_slug)

0 commit comments

Comments
 (0)