Repository: tox-dev/tox

Issue:
pass_env does not support comments
Comments can be used to comment reason for setting values, see example in ``tox4 c -e py39-core`` of ansible-lint. Perhaps within the ini file we should always remove trailing comments on every type of input.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=h1) Report
> Merging [#1889](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=desc) (b66336b) into [rewrite](https://codecov.io/gh/tox-dev/tox/commit/8cbce69895511e989ed3677c2fb953005701bcd0?el=desc) (8cbce69) will **increase** coverage by `0.18%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/tox-dev/tox/pull/1889/graphs/tree.svg?width=650&height=150&src=pr&token=DYodAwDCZ5)](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=tree)

```diff
@@             Coverage Diff              @@
##           rewrite     #1889      +/-   ##
============================================
+ Coverage    99.81%   100.00%   +0.18%     
============================================
  Files          135       133       -2     
  Lines         7526      7380     -146     
  Branches       753       728      -25     
============================================
- Hits          7512      7380     -132     
+ Misses           3         0       -3     
+ Partials        11         0      -11     
```

| Flag | Coverage Δ | |
|---|---|---|
| tests | `100.00% <100.00%> (+0.18%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/tox/config/loader/ini/\_\_init\_\_.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC9jb25maWcvbG9hZGVyL2luaS9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/tox/config/sets.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC9jb25maWcvc2V0cy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/tox/session/cmd/show\_config.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC9zZXNzaW9uL2NtZC9zaG93X2NvbmZpZy5weQ==) | `100.00% <100.00%> (ø)` | |
| [tests/config/loader/ini/test\_ini\_loader.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-dGVzdHMvY29uZmlnL2xvYWRlci9pbmkvdGVzdF9pbmlfbG9hZGVyLnB5) | `100.00% <100.00%> (ø)` | |
| [tests/session/cmd/test\_show\_config.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-dGVzdHMvc2Vzc2lvbi9jbWQvdGVzdF9zaG93X2NvbmZpZy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/tox/pytest.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC9weXRlc3QucHk=) | `100.00% <0.00%> (ø)` | |
| [src/tox/tox\_env/api.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC90b3hfZW52L2FwaS5weQ==) | `100.00% <0.00%> (ø)` | |
| [src/tox/session/state.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC9zZXNzaW9uL3N0YXRlLnB5) | `100.00% <0.00%> (ø)` | |
| [src/tox/execute/request.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC9leGVjdXRlL3JlcXVlc3QucHk=) | `100.00% <0.00%> (ø)` | |
| [src/tox/util/pep517/backend.py](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree#diff-c3JjL3RveC91dGlsL3BlcDUxNy9iYWNrZW5kLnB5) | `100.00% <0.00%> (ø)` | |
| ... and [11 more](https://codecov.io/gh/tox-dev/tox/pull/1889/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=footer). Last update [8cbce69...b66336b](https://codecov.io/gh/tox-dev/tox/pull/1889?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/changelog/1831.bugfix.rst b/docs/changelog/1831.bugfix.rst
new file mode 100644
index 00000000..f5ef2133
--- /dev/null
+++ b/docs/changelog/1831.bugfix.rst
@@ -0,0 +1,1 @@
+Support aliases in show config key specification (will print with the primary key) - by :user:`gaborbernat`.
diff --git a/docs/changelog/1831.feature.rst b/docs/changelog/1831.feature.rst
new file mode 100644
index 00000000..c8baa606
--- /dev/null
+++ b/docs/changelog/1831.feature.rst
@@ -0,0 +1,2 @@
+Support comments via the ``#`` character within the ini configuration (to force a literal ``#`` use ``\#``) -
+by :user:`gaborbernat`.
diff --git a/src/tox/config/loader/ini/__init__.py b/src/tox/config/loader/ini/__init__.py
index 54eb51fa..a8867c02 100644
--- a/src/tox/config/loader/ini/__init__.py
+++ b/src/tox/config/loader/ini/__init__.py
@@ -1,4 +1,5 @@
 import inspect
+import re
 from concurrent.futures import Future
 from configparser import ConfigParser, SectionProxy
 from contextlib import contextmanager
@@ -30,11 +31,21 @@ class IniLoader(StrConvert, Loader[str]):
 
     def load_raw(self, key: str, conf: Optional[Config], env_name: Optional[str]) -> str:
         value = self._section[key]
-        collapsed_newlines = value.replace("\\\r\n", "").replace("\\\n", "")  # collapse explicit new-line escape
+        collapsed_newlines = value.replace("\r", "").replace("\\\n", "")  # collapse explicit new-line escape
+        # strip comments
+        strip_comments = "\n".join(
+            no_comment
+            for no_comment in (
+                re.sub(r"(\s)*(?<!\\)#.*", "", line)
+                for line in collapsed_newlines.split("\n")
+                if not line.startswith("#")
+            )
+            if no_comment.strip()
+        )
         if conf is None:  # conf is None when we're loading the global tox configuration file for the CLI
-            factor_filtered = collapsed_newlines  # we don't support factor and replace functionality there
+            factor_filtered = strip_comments  # we don't support factor and replace functionality there
         else:
-            factor_filtered = filter_for_env(collapsed_newlines, env_name)  # select matching factors
+            factor_filtered = filter_for_env(strip_comments, env_name)  # select matching factors
         return factor_filtered
 
     @contextmanager
diff --git a/src/tox/config/sets.py b/src/tox/config/sets.py
index 6e31a664..2cd735b9 100644
--- a/src/tox/config/sets.py
+++ b/src/tox/config/sets.py
@@ -1,4 +1,3 @@
-from collections import OrderedDict
 from pathlib import Path
 from typing import (
     TYPE_CHECKING,
@@ -36,7 +35,8 @@ class ConfigSet:
         self._conf = conf
         self.loaders: List[Loader[Any]] = []
         self._defined: Dict[str, ConfigDefinition[Any]] = {}
-        self._keys: Dict[str, None] = OrderedDict()
+        self._keys: Dict[str, None] = {}
+        self._alias: Dict[str, str] = {}
 
     def add_config(
         self,
@@ -76,6 +76,8 @@ class ConfigSet:
                 raise ValueError(f"config {key} already defined")
         else:
             self._keys[key] = None
+            for item in keys:
+                self._alias[item] = key
             for key in keys:
                 self._defined[key] = definition
         return definition
@@ -101,6 +103,9 @@ class ConfigSet:
     def __iter__(self) -> Iterator[str]:
         return iter(self._keys.keys())
 
+    def __contains__(self, item: str) -> bool:
+        return item in self._alias
+
     def unused(self) -> List[str]:
         """Return a list of keys present in the config source but not used"""
         found: Set[str] = set()
@@ -109,6 +114,9 @@ class ConfigSet:
         found -= self._defined.keys()
         return list(sorted(found))
 
+    def primary_key(self, key: str) -> str:
+        return self._alias[key]
+
 
 class CoreConfigSet(ConfigSet):
     def __init__(self, conf: "Config", root: Path) -> None:
diff --git a/src/tox/session/cmd/show_config.py b/src/tox/session/cmd/show_config.py
index 3711866f..934b5038 100644
--- a/src/tox/session/cmd/show_config.py
+++ b/src/tox/session/cmd/show_config.py
@@ -99,6 +99,7 @@ def print_conf(is_colored: bool, conf: ConfigSet, keys: Iterable[str]) -> None:
     for key in keys if keys else conf:
         if key not in conf:
             continue
+        key = conf.primary_key(key)
         try:
             value = conf[key]
             as_str, multi_line = stringify(value)


Test patch:
diff --git a/tests/config/loader/ini/test_ini_loader.py b/tests/config/loader/ini/test_ini_loader.py
index f2fb13d8..d75dfe75 100644
--- a/tests/config/loader/ini/test_ini_loader.py
+++ b/tests/config/loader/ini/test_ini_loader.py
@@ -38,3 +38,21 @@ def test_ini_loader_raw_strip_escaped_newline(mk_ini_conf: Callable[[str], Confi
     loader = IniLoader("tox", mk_ini_conf(f"[tox]{sep}a=b\\{sep} c"), [])
     result = loader.load(key="a", of_type=str, conf=None, env_name=None, chain=[], kwargs={})
     assert result == "bc"
+
+
+@pytest.mark.parametrize(
+    ["case", "result"],
+    [
+        ("# a", ""),
+        ("#", ""),
+        ("a # w", "a"),
+        ("a\t# w", "a"),
+        ("a# w", "a"),
+        ("a\\# w", "a\\# w"),
+        ("#a\n b # w\n w", "b\nw"),
+    ],
+)
+def test_ini_loader_strip_comments(mk_ini_conf: Callable[[str], ConfigParser], case: str, result: str) -> None:
+    loader = IniLoader("tox", mk_ini_conf(f"[tox]\na={case}"), [])
+    outcome = loader.load(key="a", of_type=str, conf=None, env_name=None, chain=[], kwargs={})
+    assert outcome == result
diff --git a/tests/session/cmd/test_show_config.py b/tests/session/cmd/test_show_config.py
index 1cd3fcda..c0232d40 100644
--- a/tests/session/cmd/test_show_config.py
+++ b/tests/session/cmd/test_show_config.py
@@ -141,3 +141,9 @@ def test_show_config_select_only(tox_project: ToxProjectCreator) -> None:
     parser.read_string(result.out)
     sections = set(parser.sections())
     assert sections == {"testenv:.pkg", "testenv:b"}
+
+
+def test_show_config_alias(tox_project: ToxProjectCreator) -> None:
+    outcome = tox_project({"tox.ini": ""}).run("c", "-e", "py", "-k", "setenv")
+    outcome.assert_success()
+    assert "set_env = " in outcome.out