Repository: ARMmbed/mbed-tools

Issue:
This introduces a big issue:
The PR to list the interface version https://github.com/ARMmbed/mbed-tools/pull/235, shipped with v7.6.0, has a big issue.

````
$ pip list | grep mbed-tools
mbed-tools         7.6.0


$ mbedtools detect
Traceback (most recent call last):
  File "xxx\appdata\local\programs\python\python37\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "xxx\appdata\local\programs\python\python37\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "xxx\AppData\Local\Programs\Python\Python37\Scripts\mbedtools.exe\__main__.py", line 7, in <module>
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\click\core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\click\core.py", line 782, in main
    rv = self.invoke(ctx)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\cli\main.py", line 38, in invoke
    super().invoke(context)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\click\core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\click\core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\click\core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\cli\list_connected_devices.py", line 29, in list_connected_devices
    connected_devices = get_connected_devices()
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\devices\devices.py", line 25, in get_connected_devices
    device = Device.from_candidate(candidate_device)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\devices\device.py", line 48, in from_candidate
    device_file_info = read_device_files(candidate.mount_points)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\devices\_internal\file_parser.py", line 121, in read_device_files
    details_txt_contents = _read_first_details_txt_contents(device_file_paths)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\devices\_internal\file_parser.py", line 162, in _read_first_details_txt_contents
    return _read_details_txt(contents)
  File "xxx\appdata\local\programs\python\python37\lib\site-packages\mbed_tools\devices\_internal\file_parser.py", line 180, in _read_details_txt
    key, value = line.split(":", maxsplit=1)
ValueError: not enough values to unpack (expected 2, got 1)

$ mbedls
| platform_name | platform_name_unique | mount_point | serial_port | target_id                | interface_version |
|---------------|----------------------|-------------|-------------|--------------------------|-------------------|
| NUCLEO_F103RB | NUCLEO_F103RB[0]     | D:          | COM15       | 07000221283E5EF4177AC8C9 | V2J37M26          |


````

_Originally posted by @jeromecoutant in https://github.com/ARMmbed/mbed-tools/issues/235#issuecomment-811982174_

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/ARMmbed/mbed-tools/pull/259?src=pr&el=h1) Report
> Merging [#259](https://codecov.io/gh/ARMmbed/mbed-tools/pull/259?src=pr&el=desc) (a663bbd) into [master](https://codecov.io/gh/ARMmbed/mbed-tools/commit/a9b0e1fa3e1d58f80eff5fcc4f82f5952acf8f96?el=desc) (a9b0e1f) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/ARMmbed/mbed-tools/pull/259/graphs/tree.svg?width=650&height=150&src=pr&token=QFRIdRv91G)](https://codecov.io/gh/ARMmbed/mbed-tools/pull/259?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #259   +/-   ##
=======================================
  Coverage   97.07%   97.07%           
=======================================
  Files          92       92           
  Lines        2768     2769    +1     
=======================================
+ Hits         2687     2688    +1     
  Misses         81       81           
```


| [Impacted Files](https://codecov.io/gh/ARMmbed/mbed-tools/pull/259?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/mbed\_tools/devices/\_internal/file\_parser.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/259/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvZGV2aWNlcy9faW50ZXJuYWwvZmlsZV9wYXJzZXIucHk=) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/news/258.bugfix b/news/258.bugfix
new file mode 100644
index 0000000..cd7d099
--- /dev/null
+++ b/news/258.bugfix
@@ -0,0 +1,1 @@
+Make details.txt parsing more lenient to avoid issues with various formats.
diff --git a/src/mbed_tools/devices/_internal/file_parser.py b/src/mbed_tools/devices/_internal/file_parser.py
index baea039..b39622d 100644
--- a/src/mbed_tools/devices/_internal/file_parser.py
+++ b/src/mbed_tools/devices/_internal/file_parser.py
@@ -214,8 +214,9 @@ def _read_details_txt(file_contents: str) -> dict:
         if line.startswith("#"):
             continue
 
-        key, value = line.split(":", maxsplit=1)
-        output[key.strip()] = value.strip()
+        key, sep, value = line.partition(":")
+        if key and value:
+            output[key.strip()] = value.strip()
 
     # Some forms of details.txt use Interface Version instead of Version as the key for the version number field
     if "Interface Version" in output and "Version" not in output:


Test patch:
diff --git a/tests/devices/_internal/test_file_parser.py b/tests/devices/_internal/test_file_parser.py
index 7211c81..4872623 100644
--- a/tests/devices/_internal/test_file_parser.py
+++ b/tests/devices/_internal/test_file_parser.py
@@ -233,8 +233,11 @@ class TestReadDetailsTxt:
             build_short_details_txt(version="0777", commit_sha="99789s", local_mods="No"),
             build_long_details_txt(),
             ("", {}),
+            ("\n", {}),
+            ("blablablablaandbla", {}),
+            ("blablabla\nblaandbla\nversion : 2\n\n", {"version": "2"}),
         ),
-        ids=("short", "short2", "long", "empty"),
+        ids=("short", "short2", "long", "empty", "newline", "nosep", "multiline"),
     )
     def test_parses_details_txt(self, content, expected, tmp_path):
         details_file_path = pathlib.Path(tmp_path, "DETAILS.txt")