Repository: tldr-pages/tldr-python-client

Issue:
Multiple duplicate platform values for `du` command
Multiple duplicate platform values Found 3 pages with the same name under the platforms: osx, osx, osx.

```shell
$ tldr du                                                                                                     

  du

  Disk usage: estimate and summarize file and directory space usage.
  More information: https://www.gnu.org/software/coreutils/du.

  - List the sizes of a directory and any subdirectories, in the given unit (B/KiB/MiB):
    du -b|k|m path/to/directory

  - List the sizes of a directory and any subdirectories, in human-readable form (i.e. auto-selecting the appropriate unit for each size):
    du -h path/to/directory

  - Show the size of a single directory, in human-readable units:
    du -sh path/to/directory

  - List the human-readable sizes of a directory and of all the files and directories within it:
    du -ah path/to/directory

  - List the human-readable sizes of a directory and any subdirectories, up to N levels deep:
    du -h --max-depth=N path/to/directory

  - List the human-readable size of all `.jpg` files in subdirectories of the current directory, and show a cumulative total at the end:
    du -ch */*.jpg

  - List all files and directories (including hidden ones) above a certain [t]hreshold size (useful for investigating what is actually taking up the space):
    du --all --human-readable --threshold 1G|1024M|1048576K .[^.]* *

Found 3 pages with the same name under the platforms: osx, osx, osx.
```


link [tldr-pages issue](https://github.com/tldr-pages/tldr/issues/15468)

Hints from issue comments:
@sellisd: Same happens for other commands, not only `du` e.g.:
```
$>tldr cd
[...]
Found 2 pages with the same name under the platforms: windows, windows.
```
and
```
$>tldr w
[...]
Found 4 pages with the same name under the platforms: common, osx, osx, osx.
```


Solution patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8ac6b8c..5ace460 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -3,4 +3,4 @@ updates:
   - package-ecosystem: "github-actions"
     directory: "/"
     schedule:
-      interval: "monthly"
+      interval: "weekly"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index be62097..a80a69b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,5 +1,6 @@
-# This workflow will upload a Python Package using Twine when a release is created
+# This workflow will upload a Python Package using Trusted Publishers automatically when a release is created
 # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
+# and https://docs.pypi.org/trusted-publishers/using-a-publisher/.
 
 name: Upload Python Package
 
@@ -8,14 +9,22 @@ on:
     types: [created]
 
 jobs:
-  deploy:
-
+  pypi-publish:
     runs-on: ubuntu-latest
+
+    environment: 
+      name: pypi
+      url: https://pypi.org/project/tldr/
+
+    permissions:
+      contents: read
+      id-token: write # Required for accessing OpenID Connect (OIDC) token for PyPI trusted publisher
+
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
     - name: Set up Python
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
       with:
         python-version: '3.8'
 
@@ -56,7 +65,4 @@ jobs:
         .
 
     - name: Publish package
-      uses: pypa/gh-action-pypi-publish@release/v1
-      with:
-        user: __token__
-        password: ${{ secrets.PYPI_PASSWORD }}
+      uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4afde35..99ed718 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,17 +4,18 @@ on: ['push', 'pull_request']
 
 jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: ${{ matrix.os }}
 
     strategy:
       matrix:
+        os: ['ubuntu-latest', 'ubuntu-24.04-arm']
         python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy3.9', 'pypy3.10']
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -55,15 +56,22 @@ jobs:
         tldr --version
 
   build-snap:
-    runs-on: ubuntu-latest
+    runs-on: ${{ matrix.os }}
+    needs: ['build']
+
+    strategy:
+      matrix:
+        os: ['ubuntu-latest', 'ubuntu-24.04-arm']
 
     steps:
      - uses: actions/checkout@v4
-     - uses: snapcore/action-build@v1
+
+     - uses: canonical/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0
        id: snapcraft-build
        with:
          snapcraft-args: "-v"
-     - uses: actions/upload-artifact@v4
+
+     - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: ${{ steps.snapcraft-build.outputs.snap }}
          path: ${{ steps.snapcraft-build.outputs.snap }}
diff --git a/README.md b/README.md
index 546e45a..99f804a 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,8 @@ export TLDR_LANGUAGE="es"
 export TLDR_CACHE_ENABLED=1
 export TLDR_CACHE_MAX_AGE=720
 export TLDR_PAGES_SOURCE_LOCATION="https://raw.githubusercontent.com/tldr-pages/tldr/main/pages"
-export TLDR_DOWNLOAD_CACHE_LOCATION="https://tldr-pages.github.io/assets/tldr.zip"
+export TLDR_DOWNLOAD_CACHE_LOCATION="https://github.com/tldr-pages/tldr/releases/latest/download/tldr.zip"
+export TLDR_OPTIONS=short
 ```
 
 ### Cache
@@ -136,6 +137,10 @@ For networks that sit behind a proxy, it may be necessary to disable SSL verific
 
 will disable SSL certificate inspection. This __should be avoided__ unless absolutely necessary.
 
+Alternatively, It is possible to use a different certificate store/bundle by setting:
+
+* `TLDR_CERT=/path/to/certificates.crt`
+
 ### Colors
 
 Values of the `TLDR_COLOR_x` variables may consist of three parts:
@@ -180,3 +185,7 @@ can either use the `--source` flag when using tldr or by specifying the followin
   - it can also point to a local directory using `file:///path/to/directory`.
 - `TLDR_DOWNLOAD_CACHE_LOCATION` to control where to pull a zip of all pages from.
   - defaults to `https://github.com/tldr-pages/tldr/releases/latest/download/tldr.zip`.
+
+### Command options
+
+Pages might contain `{{[*|*]}}` patterns to let the client decide whether to show shortform or longform versions of options. This can be configured with `TLDR_OPTIONS`, which accepts values `short`, `long` and `both`
diff --git a/tldr.py b/tldr.py
index 3749988..82181c6 100755
--- a/tldr.py
+++ b/tldr.py
@@ -34,12 +34,16 @@ DOWNLOAD_CACHE_LOCATION = os.environ.get(
 USE_NETWORK = int(os.environ.get('TLDR_NETWORK_ENABLED', '1')) > 0
 USE_CACHE = int(os.environ.get('TLDR_CACHE_ENABLED', '1')) > 0
 MAX_CACHE_AGE = int(os.environ.get('TLDR_CACHE_MAX_AGE', 24*7))
+CAFILE = None if os.environ.get('TLDR_CERT', None) is None else \
+    Path(os.environ.get('TLDR_CERT')).expanduser()
 
 URLOPEN_CONTEXT = None
 if int(os.environ.get('TLDR_ALLOW_INSECURE', '0')) == 1:
     URLOPEN_CONTEXT = ssl.create_default_context()
     URLOPEN_CONTEXT.check_hostname = False
     URLOPEN_CONTEXT.verify_mode = ssl.CERT_NONE
+elif CAFILE:
+    URLOPEN_CONTEXT = ssl.create_default_context(cafile=CAFILE)
 
 OS_DIRECTORIES = {
     "android": "android",
@@ -205,7 +209,7 @@ def get_platform() -> str:
 
 
 def get_platform_list() -> List[str]:
-    platforms = ['common'] + list(OS_DIRECTORIES.values())
+    platforms = ['common'] + list(set(OS_DIRECTORIES.values()))
     current_platform = get_platform()
     platforms.remove(current_platform)
     platforms.insert(0, current_platform)
@@ -415,7 +419,7 @@ def colors_of(key: str) -> Tuple[str, str, List[str]]:
     return (color, on_color, attrs)
 
 
-def output(page: str, plain: bool = False) -> None:
+def output(page: str, display_option_length: str, plain: bool = False) -> None:
     def emphasise_example(x: str) -> str:
         # Use ANSI escapes to enable italics at the start and disable at the end
         # Also use the color yellow to differentiate from the default green
@@ -478,6 +482,12 @@ def output(page: str, plain: bool = False) -> None:
             line = line.replace(r'\{\{', '__ESCAPED_OPEN__')
             line = line.replace(r'\}\}', '__ESCAPED_CLOSE__')
 
+            # Extract long or short options from placeholders
+            if display_option_length == "short":
+                line = re.sub(r'{{\[([^|]+)\|[^|]+?\]}}', r'\1', line)
+            elif display_option_length == "long":
+                line = re.sub(r'{{\[[^|]+\|([^|]+?)\]}}', r'\1', line)
+
             elements = [' ' * 2 * LEADING_SPACES_NUM]
             for item in COMMAND_SPLIT_REGEX.split(line):
                 item, replaced = PARAM_REGEX.subn(
@@ -603,6 +613,16 @@ def create_parser() -> ArgumentParser:
                         action='store_true',
                         help='Just print the plain page file.')
 
+    parser.add_argument('--short-options',
+                        default=False,
+                        action="store_true",
+                        help='Display shortform options over longform')
+
+    parser.add_argument('--long-options',
+                        default=False,
+                        action="store_true",
+                        help='Display longform options over shortform')
+
     parser.add_argument(
         'command', type=str, nargs='*', help="command to lookup", metavar='command'
     ).complete = {"bash": "shtab_tldr_cmd_list", "zsh": "shtab_tldr_cmd_list"}
@@ -623,7 +643,20 @@ def main() -> None:
     parser = create_parser()
 
     options = parser.parse_args()
-
+    display_option_length = "long"
+    if not (options.short_options or options.long_options):
+        if os.environ.get('TLDR_OPTIONS') == "short":
+            display_option_length = "short"
+        elif os.environ.get('TLDR_OPTIONS') == "long":
+            display_option_length = "long"
+        elif os.environ.get('TLDR_OPTIONS') == "both":
+            display_option_length = "both"
+    if options.short_options:
+        display_option_length = "short"
+    if options.long_options:
+        display_option_length = "long"
+    if options.short_options and options.long_options:
+        display_option_length = "both"
     colorama.init(strip=options.color)
     if options.color is False:
         os.environ["FORCE_COLOR"] = "true"
@@ -642,6 +675,7 @@ def main() -> None:
             if file_path.exists():
                 with file_path.open(encoding='utf-8') as open_file:
                     output(open_file.read().encode('utf-8').splitlines(),
+                           display_option_length,
                            plain=options.markdown)
     elif options.search:
         search_term = options.search.lower()
@@ -675,7 +709,7 @@ def main() -> None:
                     " send a pull request to: https://github.com/tldr-pages/tldr"
                 ).format(cmd=command))
             else:
-                output(results[0][0], plain=options.markdown)
+                output(results[0][0], display_option_length, plain=options.markdown)
                 if results[1:]:
                     platforms_str = [result[1] for result in results[1:]]
                     are_multiple_platforms = len(platforms_str) > 1


Test patch:
diff --git a/tests/test_tldr.py b/tests/test_tldr.py
index 772735c..9fd713a 100644
--- a/tests/test_tldr.py
+++ b/tests/test_tldr.py
@@ -21,7 +21,7 @@ def test_whole_page(page_name, monkeypatch):
             sys.stdout = io.StringIO()
             sys.stdout.buffer = types.SimpleNamespace()
             sys.stdout.buffer.write = lambda x: sys.stdout.write(x.decode("utf-8"))
-            tldr.output(f_original)
+            tldr.output(f_original, "both")
 
             sys.stdout.seek(0)
             tldr_output = sys.stdout.read().encode("utf-8")
@@ -39,7 +39,7 @@ def test_markdown_mode(page_name):
         sys.stdout = io.StringIO()
         sys.stdout.buffer = types.SimpleNamespace()
         sys.stdout.buffer.write = lambda x: sys.stdout.write(x.decode("utf-8"))
-        tldr.output(d_original.splitlines(), plain=True)
+        tldr.output(d_original.splitlines(), "both", plain=True)
 
         sys.stdout.seek(0)
         tldr_output = sys.stdout.read().encode("utf-8")