Repository: lebrice/SimpleParsing

Issue:
Help does not appear if using metadata in fields
**Describe the bug**
Hi, it seems, that when the dataclass has metadata defined through `field` (from `simple_parsing`, of course), the help in form of comments does not get picked up.

**To Reproduce**
```python
from simple_parsing import ArgumentParser, field
from dataclasses import dataclass

@dataclass
class InputArgs:
    # Start date from which to collect data about base users. Input in iso format (YYYY-MM-DD).
    # The date is included in the data
    start_date: str = field(alias="s", metadata={'a':'b'})

    # End date for collecting base users. Input in iso format (YYYY-MM-DD). The date is included in the data.
    # Should not be before `start_date`
    end_date: str = field(alias="e")

def parse_args() -> InputArgs:
    parser = ArgumentParser("Prepare input data for training")
    parser.add_arguments(InputArgs, dest="args")

    args = parser.parse_args()
    return args.args

if __name__ == "__main__":
    parsed_args = parse_args()
    print(parsed_args)
```

`python test.py -h`

## Output
```
usage: Prepare input data for training [-h] -s str -e str

optional arguments:
  -h, --help            show this help message and exit

InputArgs ['args']:
  InputArgs(start_date: str, end_date: str)

  -s str, --start_date str
  -e str, --end_date str
                        Start date from which to collect data about base
                        users. Input in iso format (YYYY-MM-DD). The date is
                        included in the data End date for collecting base
                        users. Input in iso format (YYYY-MM-DD). The date is
                        included in the data. Should not be before
                        `start_date` (default: None)
```
## Expected output:
```
usage: Prepare input data for training [-h] -s str -e str

optional arguments:
  -h, --help            show this help message and exit

InputArgs ['args']:
  InputArgs(start_date: str, end_date: str)

  -s str, --start_date str
                        Start date from which to collect data about base
                        users. Input in iso format (YYYY-MM-DD). The date is
                        included in the data (default: None)
  -e str, --end_date str
                        End date for collecting base users. Input in iso
                        format (YYYY-MM-DD). The date is included in the data.
                        Should not be before `start_date` (default: None)
```

Discussion:

Patch:
diff --git a/simple_parsing/conflicts.py b/simple_parsing/conflicts.py
index 3d7af91..8efd80c 100644
--- a/simple_parsing/conflicts.py
+++ b/simple_parsing/conflicts.py
@@ -119,6 +119,8 @@ class ConflictResolver:
             else:
                 field_wrappers.extend(w.fields)
 
+        # TODO: #49: Also consider the conflicts with regular argparse arguments.
+
         conflicts: Dict[str, List[FieldWrapper]] = defaultdict(list)
         for field_wrapper in field_wrappers:
             for option_string in field_wrapper.option_strings:
diff --git a/simple_parsing/docstring.py b/simple_parsing/docstring.py
index 891baae..4506791 100644
--- a/simple_parsing/docstring.py
+++ b/simple_parsing/docstring.py
@@ -83,7 +83,7 @@ def get_attribute_docstring(some_dataclass: Type, field_name: str) -> AttributeD
         return AttributeDocString()
 
 def _contains_attribute_definition(line_str: str) -> bool:
-    """Returns wether or not a line contains a an class attribute definition.
+    """Returns wether or not a line contains a an dataclass field definition.
     
     Arguments:
         line_str {str} -- the line content
@@ -93,8 +93,11 @@ def _contains_attribute_definition(line_str: str) -> bool:
     """
     parts = line_str.split("#", maxsplit=1)
     before_comment = parts[0].strip()
-    parts = before_comment.split(":")
+
+    before_first_equal = before_comment.split("=", maxsplit=1)[0]
+    parts = before_first_equal.split(":")
     if len(parts) != 2:
+        # For now, I don't think it's possible to have a type annotation contain :
         return False
     attr_name = parts[0]
     attr_type = parts[1]
diff --git a/simple_parsing/wrappers/dataclass_wrapper.py b/simple_parsing/wrappers/dataclass_wrapper.py
index 5cc9785..62b3826 100644
--- a/simple_parsing/wrappers/dataclass_wrapper.py
+++ b/simple_parsing/wrappers/dataclass_wrapper.py
@@ -24,7 +24,8 @@ class DataclassWrapper(Wrapper[Dataclass]):
         # super().__init__(dataclass, name)
         self.dataclass = dataclass
         self._name = name
-        self.default = default
+        self.default = default 
+        self.prefix = prefix
 
         self.fields: List[FieldWrapper] = []
         self._destinations: List[str] = []
@@ -75,7 +76,7 @@ class DataclassWrapper(Wrapper[Dataclass]):
 
             else:
                 # a normal attribute
-                field_wrapper = FieldWrapper(field, parent=self)
+                field_wrapper = FieldWrapper(field, parent=self, prefix=self.prefix)
                 logger.debug(f"wrapped field at {field_wrapper.dest} has a default value of {field_wrapper.default}")
                 self.fields.append(field_wrapper)
         


Test patch:
diff --git a/test/conftest.py b/test/conftest.py
index d821c60..3fe8689 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -48,6 +48,15 @@ def simple_attribute(request):
     logging.debug(f"Attribute type: {some_type}, passed value: '{passed_value}', expected: '{expected_value}'")
     return request.param
 
+@pytest.fixture
+def assert_equals_stdout(capsys):
+    def strip(string): return "".join(string.split())
+
+    def should_equal(expected: str, file_path: str=None):
+        out = capsys.readouterr().out
+        assert strip(out) == strip(expected), file_path
+    return should_equal
+
 
 @pytest.fixture(scope="module")
 def parser():
diff --git a/test/test_issue_46.py b/test/test_issue_46.py
new file mode 100644
index 0000000..5538fff
--- /dev/null
+++ b/test/test_issue_46.py
@@ -0,0 +1,99 @@
+from dataclasses import dataclass
+import simple_parsing
+import textwrap
+import pytest
+
+
+@dataclass
+class JBuildRelease:
+    id: int 
+    url: str  
+    docker_image: str  
+
+
+def test_issue_46(assert_equals_stdout):
+    parser = simple_parsing.ArgumentParser()
+    parser.add_argument('--run_id', type=str)
+    parser.add_arguments(JBuildRelease, dest="jbuild", prefix="jbuild")
+    
+    parser.print_help()
+    
+    assert_equals_stdout(textwrap.dedent("""\
+        usage: pytest [-h] [--run_id str] --jbuildid int --jbuildurl str
+                      --jbuilddocker_image str
+        
+        optional arguments:
+          -h, --help            show this help message and exit
+          --run_id str
+        
+        JBuildRelease ['jbuild']:
+          JBuildRelease(id:int, url:str, docker_image:str)
+        
+          --jbuildid int
+          --jbuildurl str
+          --jbuilddocker_image str
+        """
+    ))
+    from .testutils import raises_missing_required_arg
+    with raises_missing_required_arg():
+        args = parser.parse_args("--id 123 --jbuild.id 456 --jbuild.url bob --jbuild.docker_image foo".split())
+
+
+def test_issue_46_solution2(assert_equals_stdout):
+    # This (now) works:
+    parser = simple_parsing.ArgumentParser(add_dest_to_option_strings=True)
+    parser.add_argument('--run_id', type=str)
+    parser.add_arguments(JBuildRelease, dest="jbuild", prefix="jbuild.")
+    
+    parser.print_help()
+    assert_equals_stdout(textwrap.dedent("""\
+        usage: pytest [-h] [--run_id str] --jbuild.id int --jbuild.url str
+                      --jbuild.docker_image str
+
+        optional arguments:
+          -h, --help            show this help message and exit
+          --run_id str
+
+        JBuildRelease ['jbuild']:
+          JBuildRelease(id:int, url:str, docker_image:str)
+
+          --jbuild.id int
+          --jbuild.url str
+          --jbuild.docker_image str
+        """
+    ))
+
+
+@pytest.mark.xfail(reason="TODO: Issue #49")
+def test_conflict_with_regular_argparse_arg():
+    # This _should_ work, but it doesn't, adding a new issue for this:
+    # the problem: SimpleParsing doesn't yet detect 
+    # conflicts between arguments added the usual way with `add_argument` and those
+    # added through `add_arguments`.
+    parser = simple_parsing.ArgumentParser()
+    parser.add_argument('--id', type=str)
+    parser.add_arguments(JBuildRelease, dest="jbuild")
+    args = parser.parse_args("--id 123 --jbuild.id 456 --jbuild.url bob --jbuild.docker_image foo".split())
+    assert args.id == 123
+    assert args.jbuild.id == 456
+
+
+@pytest.mark.xfail(reason="TODO: Issue #49")
+def test_workaround():
+    from simple_parsing import mutable_field, ConflictResolution
+    # This also doesn't work, since the prefix is only added to the 'offending'
+    # argument, rather than to all the args in that group. 
+    @dataclass
+    class Main:
+        id: int
+        jbuild: JBuildRelease
+    
+    parser = simple_parsing.ArgumentParser()
+    parser.add_arguments(Main, "main")
+    args = parser.parse_args("--id 123 --jbuild.id 456 --jbuild.url bob --jbuild.docker_image foo".split())
+    args = args.main
+    assert args.id == 123
+    assert args.jbuild.id == 456
+    
+    
+    
\ No newline at end of file
diff --git a/test/test_issue_48.py b/test/test_issue_48.py
new file mode 100644
index 0000000..4ddc2d8
--- /dev/null
+++ b/test/test_issue_48.py
@@ -0,0 +1,45 @@
+from simple_parsing import ArgumentParser, field
+from dataclasses import dataclass
+
+@dataclass
+class InputArgs:
+    # Start date from which to collect data about base users. Input in iso format (YYYY-MM-DD).
+    # The date is included in the data
+    start_date: str = field(alias="s", metadata={'a':'b'})
+
+    # End date for collecting base users. Input in iso format (YYYY-MM-DD). The date is included in the data.
+    # Should not be before `start_date`
+    end_date: str = field(alias="e")
+
+
+from io import StringIO
+import textwrap
+
+
+def test_issue_48():
+    parser = ArgumentParser("Prepare input data for training")
+    parser.add_arguments(InputArgs, dest="args")
+    s = StringIO()
+    parser.print_help(file=s)
+    s.seek(0)
+    assert s.read().replace(" ", "") == textwrap.dedent("""\
+        usage: Prepare input data for training [-h] -s str -e str
+
+        optional arguments:
+          -h, --help            show this help message and exit
+        
+        InputArgs ['args']:
+          InputArgs(start_date:str, end_date:str)
+        
+          -s str, --start_date str
+                                Start date from which to collect data about base
+                                users. Input in iso format (YYYY-MM-DD). The date is
+                                included in the data (default: None)
+          -e str, --end_date str
+                                End date for collecting base users. Input in iso
+                                format (YYYY-MM-DD). The date is included in the data.
+                                Should not be before `start_date` (default: None)
+        """).replace(" ", "")
+    
+    
+    # args = parser.parse_args()
\ No newline at end of file