Repository: pdm-project/pdm

Issue:
pdm list contains full license text
- [X] I have searched the issue tracker and believe that this is not a duplicate.


The full license text sometimes appears in the output of `pdm list`

This is because the license file is somtimes in the package medata.

```
license = { file="LICENSE" } in pyproject.toml
```


## Steps to reproduce

```powershell
pdm list --fields name,version,licenses
```


## Actual behavior

 See how the full license text appears in the search export.

```
╭────────────────────────────┬────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────╮
│ name                       │ version                        │ licenses                                                                      │
├────────────────────────────┼────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ Arpeggio                   │ 2.0.0                          │ MIT                                                                           │
│ arrow                      │ 1.2.3                          │ Apache 2.0                                                                    │
│ attrs                      │ 23.1.0                         │ MIT License                                                                   │
│ binaryornot                │ 0.4.4                          │ BSD                                                                           │
...
│ platformdirs               │ 3.10.0                         │ MIT License                                                                   │
│ pluggy                     │ 1.2.0                          │ MIT                                                                           │
│ plumbum                    │ 1.8.2                          │ Copyright (c) 2013 Tomer Filiba (tomerfiliba@gmail.com)                       │
│                            │                                │                                                                               │
│                            │                                │ Permission is hereby granted, free of charge, to any person obtaining a copy  │
│                            │                                │ of this software and associated documentation files (the "Software"), to deal │
│                            │                                │ in the Software without restriction, including without limitation the rights  │
│                            │                                │ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell     │
```


## Expected behavior

The list should only include the short form name of the license.




Discussion:

Patch:
diff --git a/news/2538.bugfix.md b/news/2538.bugfix.md
new file mode 100644
index 0000000000..d7bea4358f
--- /dev/null
+++ b/news/2538.bugfix.md
@@ -0,0 +1,1 @@
+Fixed pdm list output containing full license text in some cases
\ No newline at end of file
diff --git a/src/pdm/cli/commands/list.py b/src/pdm/cli/commands/list.py
index 01f0505cc9..0ed311a499 100644
--- a/src/pdm/cli/commands/list.py
+++ b/src/pdm/cli/commands/list.py
@@ -340,7 +340,15 @@ def __init__(self, dist: im.Distribution, groups: set[str]):
         # so generate a pipe separated list (to avoid complexity with CSV export).
         self.licenses: str | None = dist.metadata["License"]
         self.licenses = None if self.licenses == "UNKNOWN" else self.licenses
-        if not self.licenses:
+
+        # Sometimes package metadata contains the full license text.
+        # e.g. license = { file="LICENSE" } in pyproject.toml
+        # To identify this, check for newlines or very long strings.
+        # 50 chars is picked because the longest OSI license (WTFPL) full name is 43 characters.
+        is_full_text = self.licenses and "\n" in self.licenses or len(self.licenses or "") > 50
+
+        # If that is the case, look at the classifiers instead.
+        if not self.licenses or is_full_text:
             classifier_licenses = [v for v in dist.metadata.get_all("Classifier", []) if v.startswith("License")]
             alternatives = [parts.split("::") for parts in classifier_licenses]
             alternatives = [part[-1].strip() for part in alternatives if part]


Test patch:
diff --git a/tests/cli/test_list.py b/tests/cli/test_list.py
index 49615b6596..3bd56dea13 100644
--- a/tests/cli/test_list.py
+++ b/tests/cli/test_list.py
@@ -513,8 +513,16 @@ class _MockPackagePath(pathlib.PurePosixPath):
         def read_text(self, *args, **kwargs):
             return self.license_text
 
-    # Foo package.
-    foo = Distribution("foo", "0.1.0", metadata={"License": "A License"})
+    # Foo package (adapted to contain newlines in License field)
+    # e.g. via license = { file="LICENSE" }
+    foo = Distribution(
+        "foo",
+        "0.1.0",
+        metadata={
+            "License": "A License\n\nextra\ntext",
+            "Classifier": "License :: A License",
+        },
+    )
     foo_l = _MockPackagePath("foo-0.1.0.dist-info", "LICENSE")
     foo_l.license_text = "license text for foo here"
     foo.files = [foo_l]
@@ -679,6 +687,9 @@ def test_list_json_fields_licences(project, pdm):
 
 @pytest.mark.usefixtures("fake_working_set")
 def test_list_markdown_fields_licences(project, pdm):
+    # Note that in "foo" the "License" metadata field ("License": "A License\n\nextra\ntext")
+    # is ignored, in favour of the classifier and the LICENSE file.
+    # This behaviour could be improved.
     result = pdm(["list", "--markdown", "--fields", "name,version,licenses"], obj=project)
     expected = (
         "# test-project licenses\n"