We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
cargo fails to find standard homedir path for cargo binary
### Summary

the cargo module fails with -

`TASK [Install tokei Rust package] ***************************************************************************************************************************
fatal: [hostname]: FAILED! => {"changed": false, "msg": "Failed to find required executable \"cargo\" in paths: /usr/local/bin:/usr/bin:/bin:/usr/games:/sbin:/usr/sbin:/usr/local/sbin"}`

cargo executable is located in default rustup install location `/home/username/.cargo/bin/` 

### Issue Type

Bug Report

### Component Name

cargo

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.12.3]
  config file = /home/username/foo/ansible.cfg
  configured module search path = ['/home/username/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/username/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.10 (main, Feb 22 2022, 13:54:07) [GCC 11.2.0]
  jinja version = 3.0.3
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general

# /usr/lib/python3/dist-packages/ansible_collections
Collection        Version
----------------- -------
community.general 4.5.0  
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
DEFAULT_HOST_LIST(/home/username/foo/ansible.cfg) = ['/home/username/foo/HOSTS']
```


### OS / Environment

Debian Bookworm targeting Bookworm

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)

```
Run this module against home directory with the default rustup install location.

### Expected Results

cargo installs the package

### Actual Results

```console (paste below)
TASK [Install tokei Rust package] ***************************************************************************************************************************
fatal: [hostname]: FAILED! => {"changed": false, "msg": "Failed to find required executable \"cargo\" in paths: /usr/local/bin:/usr/bin:/bin:/usr/games:/sbin:/usr/sbin:/usr/local/sbin"}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `plugins/modules/cargo.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # Copyright (c) 2021 Radek Sprta <mail@radeksprta.eu>
4 # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
5 # SPDX-License-Identifier: GPL-3.0-or-later
6 
7 from __future__ import absolute_import, division, print_function
8 
9 __metaclass__ = type
10 
11 
12 DOCUMENTATION = r"""
13 ---
14 module: cargo
15 short_description: Manage Rust packages with cargo
16 version_added: 4.3.0
17 description:
18   - Manage Rust packages with cargo.
19 author: "Radek Sprta (@radek-sprta)"
20 extends_documentation_fragment:
21   - community.general.attributes
22 attributes:
23   check_mode:
24     support: full
25   diff_mode:
26     support: none
27 options:
28   name:
29     description:
30       - The name of a Rust package to install.
31     type: list
32     elements: str
33     required: true
34   path:
35     description:
36       ->
37       The base path where to install the Rust packages. Cargo automatically appends
38       V(/bin). In other words, V(/usr/local) will become V(/usr/local/bin).
39     type: path
40   version:
41     description:
42       ->
43       The version to install. If O(name) contains multiple values, the module will
44       try to install all of them in this version.
45     type: str
46     required: false
47   locked:
48     description:
49       - Install with locked dependencies.
50       - This is only used when installing packages.
51     required: false
52     type: bool
53     default: false
54     version_added: 7.5.0
55   state:
56     description:
57       - The state of the Rust package.
58     required: false
59     type: str
60     default: present
61     choices: [ "present", "absent", "latest" ]
62 requirements:
63     - cargo installed in bin path (recommended /usr/local/bin)
64 """
65 
66 EXAMPLES = r"""
67 - name: Install "ludusavi" Rust package
68   community.general.cargo:
69     name: ludusavi
70 
71 - name: Install "ludusavi" Rust package with locked dependencies
72   community.general.cargo:
73     name: ludusavi
74     locked: true
75 
76 - name: Install "ludusavi" Rust package in version 0.10.0
77   community.general.cargo:
78     name: ludusavi
79     version: '0.10.0'
80 
81 - name: Install "ludusavi" Rust package to global location
82   community.general.cargo:
83     name: ludusavi
84     path: /usr/local
85 
86 - name: Remove "ludusavi" Rust package
87   community.general.cargo:
88     name: ludusavi
89     state: absent
90 
91 - name: Update "ludusavi" Rust package its latest version
92   community.general.cargo:
93     name: ludusavi
94     state: latest
95 """
96 
97 import os
98 import re
99 
100 from ansible.module_utils.basic import AnsibleModule
101 
102 
103 class Cargo(object):
104     def __init__(self, module, **kwargs):
105         self.module = module
106         self.name = kwargs["name"]
107         self.path = kwargs["path"]
108         self.state = kwargs["state"]
109         self.version = kwargs["version"]
110         self.locked = kwargs["locked"]
111 
112         self.executable = [module.get_bin_path("cargo", True)]
113 
114     @property
115     def path(self):
116         return self._path
117 
118     @path.setter
119     def path(self, path):
120         if path is not None and not os.path.isdir(path):
121             self.module.fail_json(msg="Path %s is not a directory" % path)
122         self._path = path
123 
124     def _exec(
125         self, args, run_in_check_mode=False, check_rc=True, add_package_name=True
126     ):
127         if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
128             cmd = self.executable + args
129             rc, out, err = self.module.run_command(cmd, check_rc=check_rc)
130             return out, err
131         return "", ""
132 
133     def get_installed(self):
134         cmd = ["install", "--list"]
135         data, dummy = self._exec(cmd, True, False, False)
136 
137         package_regex = re.compile(r"^([\w\-]+) v(.+):$")
138         installed = {}
139         for line in data.splitlines():
140             package_info = package_regex.match(line)
141             if package_info:
142                 installed[package_info.group(1)] = package_info.group(2)
143 
144         return installed
145 
146     def install(self, packages=None):
147         cmd = ["install"]
148         cmd.extend(packages or self.name)
149         if self.locked:
150             cmd.append("--locked")
151         if self.path:
152             cmd.append("--root")
153             cmd.append(self.path)
154         if self.version:
155             cmd.append("--version")
156             cmd.append(self.version)
157         return self._exec(cmd)
158 
159     def is_outdated(self, name):
160         installed_version = self.get_installed().get(name)
161 
162         cmd = ["search", name, "--limit", "1"]
163         data, dummy = self._exec(cmd, True, False, False)
164 
165         match = re.search(r'"(.+)"', data)
166         if match:
167             latest_version = match.group(1)
168 
169         return installed_version != latest_version
170 
171     def uninstall(self, packages=None):
172         cmd = ["uninstall"]
173         cmd.extend(packages or self.name)
174         return self._exec(cmd)
175 
176 
177 def main():
178     arg_spec = dict(
179         name=dict(required=True, type="list", elements="str"),
180         path=dict(default=None, type="path"),
181         state=dict(default="present", choices=["present", "absent", "latest"]),
182         version=dict(default=None, type="str"),
183         locked=dict(default=False, type="bool"),
184     )
185     module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
186 
187     name = module.params["name"]
188     path = module.params["path"]
189     state = module.params["state"]
190     version = module.params["version"]
191     locked = module.params["locked"]
192 
193     if not name:
194         module.fail_json(msg="Package name must be specified")
195 
196     # Set LANG env since we parse stdout
197     module.run_command_environ_update = dict(
198         LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
199     )
200 
201     cargo = Cargo(module, name=name, path=path, state=state, version=version, locked=locked)
202     changed, out, err = False, None, None
203     installed_packages = cargo.get_installed()
204     if state == "present":
205         to_install = [
206             n
207             for n in name
208             if (n not in installed_packages)
209             or (version and version != installed_packages[n])
210         ]
211         if to_install:
212             changed = True
213             out, err = cargo.install(to_install)
214     elif state == "latest":
215         to_update = [
216             n for n in name if n not in installed_packages or cargo.is_outdated(n)
217         ]
218         if to_update:
219             changed = True
220             out, err = cargo.install(to_update)
221     else:  # absent
222         to_uninstall = [n for n in name if n in installed_packages]
223         if to_uninstall:
224             changed = True
225             out, err = cargo.uninstall(to_uninstall)
226 
227     module.exit_json(changed=changed, stdout=out, stderr=err)
228 
229 
230 if __name__ == "__main__":
231     main()
232
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/plugins/modules/cargo.py b/plugins/modules/cargo.py
--- a/plugins/modules/cargo.py
+++ b/plugins/modules/cargo.py
@@ -25,6 +25,12 @@
   diff_mode:
     support: none
 options:
+  executable:
+    description:
+      - Path to the C(cargo) installed in the system.
+      - If not specified, the module will look C(cargo) in E(PATH).
+    type: path
+    version_added: 7.5.0
   name:
     description:
       - The name of a Rust package to install.
@@ -60,7 +66,7 @@
     default: present
     choices: [ "present", "absent", "latest" ]
 requirements:
-    - cargo installed in bin path (recommended /usr/local/bin)
+    - cargo installed
 """
 
 EXAMPLES = r"""
@@ -103,14 +109,13 @@
 class Cargo(object):
     def __init__(self, module, **kwargs):
         self.module = module
+        self.executable = [kwargs["executable"] or module.get_bin_path("cargo", True)]
         self.name = kwargs["name"]
         self.path = kwargs["path"]
         self.state = kwargs["state"]
         self.version = kwargs["version"]
         self.locked = kwargs["locked"]
 
-        self.executable = [module.get_bin_path("cargo", True)]
-
     @property
     def path(self):
         return self._path
@@ -176,6 +181,7 @@
 
 def main():
     arg_spec = dict(
+        executable=dict(default=None, type="path"),
         name=dict(required=True, type="list", elements="str"),
         path=dict(default=None, type="path"),
         state=dict(default="present", choices=["present", "absent", "latest"]),
@@ -185,10 +191,8 @@
     module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
 
     name = module.params["name"]
-    path = module.params["path"]
     state = module.params["state"]
     version = module.params["version"]
-    locked = module.params["locked"]
 
     if not name:
         module.fail_json(msg="Package name must be specified")
@@ -198,7 +202,7 @@
         LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
     )
 
-    cargo = Cargo(module, name=name, path=path, state=state, version=version, locked=locked)
+    cargo = Cargo(module, **module.params)
     changed, out, err = False, None, None
     installed_packages = cargo.get_installed()
     if state == "present":

{"golden_diff": "diff --git a/plugins/modules/cargo.py b/plugins/modules/cargo.py\n--- a/plugins/modules/cargo.py\n+++ b/plugins/modules/cargo.py\n@@ -25,6 +25,12 @@\n   diff_mode:\n     support: none\n options:\n+  executable:\n+    description:\n+      - Path to the C(cargo) installed in the system.\n+      - If not specified, the module will look C(cargo) in E(PATH).\n+    type: path\n+    version_added: 7.5.0\n   name:\n     description:\n       - The name of a Rust package to install.\n@@ -60,7 +66,7 @@\n     default: present\n     choices: [ \"present\", \"absent\", \"latest\" ]\n requirements:\n-    - cargo installed in bin path (recommended /usr/local/bin)\n+    - cargo installed\n \"\"\"\n \n EXAMPLES = r\"\"\"\n@@ -103,14 +109,13 @@\n class Cargo(object):\n     def __init__(self, module, **kwargs):\n         self.module = module\n+        self.executable = [kwargs[\"executable\"] or module.get_bin_path(\"cargo\", True)]\n         self.name = kwargs[\"name\"]\n         self.path = kwargs[\"path\"]\n         self.state = kwargs[\"state\"]\n         self.version = kwargs[\"version\"]\n         self.locked = kwargs[\"locked\"]\n \n-        self.executable = [module.get_bin_path(\"cargo\", True)]\n-\n     @property\n     def path(self):\n         return self._path\n@@ -176,6 +181,7 @@\n \n def main():\n     arg_spec = dict(\n+        executable=dict(default=None, type=\"path\"),\n         name=dict(required=True, type=\"list\", elements=\"str\"),\n         path=dict(default=None, type=\"path\"),\n         state=dict(default=\"present\", choices=[\"present\", \"absent\", \"latest\"]),\n@@ -185,10 +191,8 @@\n     module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)\n \n     name = module.params[\"name\"]\n-    path = module.params[\"path\"]\n     state = module.params[\"state\"]\n     version = module.params[\"version\"]\n-    locked = module.params[\"locked\"]\n \n     if not name:\n         module.fail_json(msg=\"Package name must be specified\")\n@@ -198,7 +202,7 @@\n         LANG=\"C\", LC_ALL=\"C\", LC_MESSAGES=\"C\", LC_CTYPE=\"C\"\n     )\n \n-    cargo = Cargo(module, name=name, path=path, state=state, version=version, locked=locked)\n+    cargo = Cargo(module, **module.params)\n     changed, out, err = False, None, None\n     installed_packages = cargo.get_installed()\n     if state == \"present\":\n", "issue": "cargo fails to find standard homedir path for cargo binary\n### Summary\r\n\r\nthe cargo module fails with -\r\n\r\n`TASK [Install tokei Rust package] ***************************************************************************************************************************\r\nfatal: [hostname]: FAILED! => {\"changed\": false, \"msg\": \"Failed to find required executable \\\"cargo\\\" in paths: /usr/local/bin:/usr/bin:/bin:/usr/games:/sbin:/usr/sbin:/usr/local/sbin\"}`\r\n\r\ncargo executable is located in default rustup install location `/home/username/.cargo/bin/` \r\n\r\n### Issue Type\r\n\r\nBug Report\r\n\r\n### Component Name\r\n\r\ncargo\r\n\r\n### Ansible Version\r\n\r\n```console (paste below)\r\n$ ansible --version\r\nansible [core 2.12.3]\r\n  config file = /home/username/foo/ansible.cfg\r\n  configured module search path = ['/home/username/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /usr/lib/python3/dist-packages/ansible\r\n  ansible collection location = /home/username/.ansible/collections:/usr/share/ansible/collections\r\n  executable location = /usr/bin/ansible\r\n  python version = 3.9.10 (main, Feb 22 2022, 13:54:07) [GCC 11.2.0]\r\n  jinja version = 3.0.3\r\n  libyaml = True\r\n```\r\n\r\n\r\n### Community.general Version\r\n\r\n```console (paste below)\r\n$ ansible-galaxy collection list community.general\r\n\r\n# /usr/lib/python3/dist-packages/ansible_collections\r\nCollection        Version\r\n----------------- -------\r\ncommunity.general 4.5.0  \r\n```\r\n\r\n\r\n### Configuration\r\n\r\n```console (paste below)\r\n$ ansible-config dump --only-changed\r\nDEFAULT_HOST_LIST(/home/username/foo/ansible.cfg) = ['/home/username/foo/HOSTS']\r\n```\r\n\r\n\r\n### OS / Environment\r\n\r\nDebian Bookworm targeting Bookworm\r\n\r\n### Steps to Reproduce\r\n\r\n<!--- Paste example playbooks or commands between quotes below -->\r\n```yaml (paste below)\r\n\r\n```\r\nRun this module against home directory with the default rustup install location.\r\n\r\n### Expected Results\r\n\r\ncargo installs the package\r\n\r\n### Actual Results\r\n\r\n```console (paste below)\r\nTASK [Install tokei Rust package] ***************************************************************************************************************************\r\nfatal: [hostname]: FAILED! => {\"changed\": false, \"msg\": \"Failed to find required executable \\\"cargo\\\" in paths: /usr/local/bin:/usr/bin:/bin:/usr/games:/sbin:/usr/sbin:/usr/local/sbin\"}\r\n```\r\n\r\n\r\n### Code of Conduct\r\n\r\n- [X] I agree to follow the Ansible Code of Conduct\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n# Copyright (c) 2021 Radek Sprta <mail@radeksprta.eu>\n# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import absolute_import, division, print_function\n\n__metaclass__ = type\n\n\nDOCUMENTATION = r\"\"\"\n---\nmodule: cargo\nshort_description: Manage Rust packages with cargo\nversion_added: 4.3.0\ndescription:\n  - Manage Rust packages with cargo.\nauthor: \"Radek Sprta (@radek-sprta)\"\nextends_documentation_fragment:\n  - community.general.attributes\nattributes:\n  check_mode:\n    support: full\n  diff_mode:\n    support: none\noptions:\n  name:\n    description:\n      - The name of a Rust package to install.\n    type: list\n    elements: str\n    required: true\n  path:\n    description:\n      ->\n      The base path where to install the Rust packages. Cargo automatically appends\n      V(/bin). In other words, V(/usr/local) will become V(/usr/local/bin).\n    type: path\n  version:\n    description:\n      ->\n      The version to install. If O(name) contains multiple values, the module will\n      try to install all of them in this version.\n    type: str\n    required: false\n  locked:\n    description:\n      - Install with locked dependencies.\n      - This is only used when installing packages.\n    required: false\n    type: bool\n    default: false\n    version_added: 7.5.0\n  state:\n    description:\n      - The state of the Rust package.\n    required: false\n    type: str\n    default: present\n    choices: [ \"present\", \"absent\", \"latest\" ]\nrequirements:\n    - cargo installed in bin path (recommended /usr/local/bin)\n\"\"\"\n\nEXAMPLES = r\"\"\"\n- name: Install \"ludusavi\" Rust package\n  community.general.cargo:\n    name: ludusavi\n\n- name: Install \"ludusavi\" Rust package with locked dependencies\n  community.general.cargo:\n    name: ludusavi\n    locked: true\n\n- name: Install \"ludusavi\" Rust package in version 0.10.0\n  community.general.cargo:\n    name: ludusavi\n    version: '0.10.0'\n\n- name: Install \"ludusavi\" Rust package to global location\n  community.general.cargo:\n    name: ludusavi\n    path: /usr/local\n\n- name: Remove \"ludusavi\" Rust package\n  community.general.cargo:\n    name: ludusavi\n    state: absent\n\n- name: Update \"ludusavi\" Rust package its latest version\n  community.general.cargo:\n    name: ludusavi\n    state: latest\n\"\"\"\n\nimport os\nimport re\n\nfrom ansible.module_utils.basic import AnsibleModule\n\n\nclass Cargo(object):\n    def __init__(self, module, **kwargs):\n        self.module = module\n        self.name = kwargs[\"name\"]\n        self.path = kwargs[\"path\"]\n        self.state = kwargs[\"state\"]\n        self.version = kwargs[\"version\"]\n        self.locked = kwargs[\"locked\"]\n\n        self.executable = [module.get_bin_path(\"cargo\", True)]\n\n    @property\n    def path(self):\n        return self._path\n\n    @path.setter\n    def path(self, path):\n        if path is not None and not os.path.isdir(path):\n            self.module.fail_json(msg=\"Path %s is not a directory\" % path)\n        self._path = path\n\n    def _exec(\n        self, args, run_in_check_mode=False, check_rc=True, add_package_name=True\n    ):\n        if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):\n            cmd = self.executable + args\n            rc, out, err = self.module.run_command(cmd, check_rc=check_rc)\n            return out, err\n        return \"\", \"\"\n\n    def get_installed(self):\n        cmd = [\"install\", \"--list\"]\n        data, dummy = self._exec(cmd, True, False, False)\n\n        package_regex = re.compile(r\"^([\\w\\-]+) v(.+):$\")\n        installed = {}\n        for line in data.splitlines():\n            package_info = package_regex.match(line)\n            if package_info:\n                installed[package_info.group(1)] = package_info.group(2)\n\n        return installed\n\n    def install(self, packages=None):\n        cmd = [\"install\"]\n        cmd.extend(packages or self.name)\n        if self.locked:\n            cmd.append(\"--locked\")\n        if self.path:\n            cmd.append(\"--root\")\n            cmd.append(self.path)\n        if self.version:\n            cmd.append(\"--version\")\n            cmd.append(self.version)\n        return self._exec(cmd)\n\n    def is_outdated(self, name):\n        installed_version = self.get_installed().get(name)\n\n        cmd = [\"search\", name, \"--limit\", \"1\"]\n        data, dummy = self._exec(cmd, True, False, False)\n\n        match = re.search(r'\"(.+)\"', data)\n        if match:\n            latest_version = match.group(1)\n\n        return installed_version != latest_version\n\n    def uninstall(self, packages=None):\n        cmd = [\"uninstall\"]\n        cmd.extend(packages or self.name)\n        return self._exec(cmd)\n\n\ndef main():\n    arg_spec = dict(\n        name=dict(required=True, type=\"list\", elements=\"str\"),\n        path=dict(default=None, type=\"path\"),\n        state=dict(default=\"present\", choices=[\"present\", \"absent\", \"latest\"]),\n        version=dict(default=None, type=\"str\"),\n        locked=dict(default=False, type=\"bool\"),\n    )\n    module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)\n\n    name = module.params[\"name\"]\n    path = module.params[\"path\"]\n    state = module.params[\"state\"]\n    version = module.params[\"version\"]\n    locked = module.params[\"locked\"]\n\n    if not name:\n        module.fail_json(msg=\"Package name must be specified\")\n\n    # Set LANG env since we parse stdout\n    module.run_command_environ_update = dict(\n        LANG=\"C\", LC_ALL=\"C\", LC_MESSAGES=\"C\", LC_CTYPE=\"C\"\n    )\n\n    cargo = Cargo(module, name=name, path=path, state=state, version=version, locked=locked)\n    changed, out, err = False, None, None\n    installed_packages = cargo.get_installed()\n    if state == \"present\":\n        to_install = [\n            n\n            for n in name\n            if (n not in installed_packages)\n            or (version and version != installed_packages[n])\n        ]\n        if to_install:\n            changed = True\n            out, err = cargo.install(to_install)\n    elif state == \"latest\":\n        to_update = [\n            n for n in name if n not in installed_packages or cargo.is_outdated(n)\n        ]\n        if to_update:\n            changed = True\n            out, err = cargo.install(to_update)\n    else:  # absent\n        to_uninstall = [n for n in name if n in installed_packages]\n        if to_uninstall:\n            changed = True\n            out, err = cargo.uninstall(to_uninstall)\n\n    module.exit_json(changed=changed, stdout=out, stderr=err)\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "plugins/modules/cargo.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n# Copyright (c) 2021 Radek Sprta <mail@radeksprta.eu>\n# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import absolute_import, division, print_function\n\n__metaclass__ = type\n\n\nDOCUMENTATION = r\"\"\"\n---\nmodule: cargo\nshort_description: Manage Rust packages with cargo\nversion_added: 4.3.0\ndescription:\n  - Manage Rust packages with cargo.\nauthor: \"Radek Sprta (@radek-sprta)\"\nextends_documentation_fragment:\n  - community.general.attributes\nattributes:\n  check_mode:\n    support: full\n  diff_mode:\n    support: none\noptions:\n  executable:\n    description:\n      - Path to the C(cargo) installed in the system.\n      - If not specified, the module will look C(cargo) in E(PATH).\n    type: path\n    version_added: 7.5.0\n  name:\n    description:\n      - The name of a Rust package to install.\n    type: list\n    elements: str\n    required: true\n  path:\n    description:\n      ->\n      The base path where to install the Rust packages. Cargo automatically appends\n      V(/bin). In other words, V(/usr/local) will become V(/usr/local/bin).\n    type: path\n  version:\n    description:\n      ->\n      The version to install. If O(name) contains multiple values, the module will\n      try to install all of them in this version.\n    type: str\n    required: false\n  locked:\n    description:\n      - Install with locked dependencies.\n      - This is only used when installing packages.\n    required: false\n    type: bool\n    default: false\n    version_added: 7.5.0\n  state:\n    description:\n      - The state of the Rust package.\n    required: false\n    type: str\n    default: present\n    choices: [ \"present\", \"absent\", \"latest\" ]\nrequirements:\n    - cargo installed\n\"\"\"\n\nEXAMPLES = r\"\"\"\n- name: Install \"ludusavi\" Rust package\n  community.general.cargo:\n    name: ludusavi\n\n- name: Install \"ludusavi\" Rust package with locked dependencies\n  community.general.cargo:\n    name: ludusavi\n    locked: true\n\n- name: Install \"ludusavi\" Rust package in version 0.10.0\n  community.general.cargo:\n    name: ludusavi\n    version: '0.10.0'\n\n- name: Install \"ludusavi\" Rust package to global location\n  community.general.cargo:\n    name: ludusavi\n    path: /usr/local\n\n- name: Remove \"ludusavi\" Rust package\n  community.general.cargo:\n    name: ludusavi\n    state: absent\n\n- name: Update \"ludusavi\" Rust package its latest version\n  community.general.cargo:\n    name: ludusavi\n    state: latest\n\"\"\"\n\nimport os\nimport re\n\nfrom ansible.module_utils.basic import AnsibleModule\n\n\nclass Cargo(object):\n    def __init__(self, module, **kwargs):\n        self.module = module\n        self.executable = [kwargs[\"executable\"] or module.get_bin_path(\"cargo\", True)]\n        self.name = kwargs[\"name\"]\n        self.path = kwargs[\"path\"]\n        self.state = kwargs[\"state\"]\n        self.version = kwargs[\"version\"]\n        self.locked = kwargs[\"locked\"]\n\n    @property\n    def path(self):\n        return self._path\n\n    @path.setter\n    def path(self, path):\n        if path is not None and not os.path.isdir(path):\n            self.module.fail_json(msg=\"Path %s is not a directory\" % path)\n        self._path = path\n\n    def _exec(\n        self, args, run_in_check_mode=False, check_rc=True, add_package_name=True\n    ):\n        if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):\n            cmd = self.executable + args\n            rc, out, err = self.module.run_command(cmd, check_rc=check_rc)\n            return out, err\n        return \"\", \"\"\n\n    def get_installed(self):\n        cmd = [\"install\", \"--list\"]\n        data, dummy = self._exec(cmd, True, False, False)\n\n        package_regex = re.compile(r\"^([\\w\\-]+) v(.+):$\")\n        installed = {}\n        for line in data.splitlines():\n            package_info = package_regex.match(line)\n            if package_info:\n                installed[package_info.group(1)] = package_info.group(2)\n\n        return installed\n\n    def install(self, packages=None):\n        cmd = [\"install\"]\n        cmd.extend(packages or self.name)\n        if self.locked:\n            cmd.append(\"--locked\")\n        if self.path:\n            cmd.append(\"--root\")\n            cmd.append(self.path)\n        if self.version:\n            cmd.append(\"--version\")\n            cmd.append(self.version)\n        return self._exec(cmd)\n\n    def is_outdated(self, name):\n        installed_version = self.get_installed().get(name)\n\n        cmd = [\"search\", name, \"--limit\", \"1\"]\n        data, dummy = self._exec(cmd, True, False, False)\n\n        match = re.search(r'\"(.+)\"', data)\n        if match:\n            latest_version = match.group(1)\n\n        return installed_version != latest_version\n\n    def uninstall(self, packages=None):\n        cmd = [\"uninstall\"]\n        cmd.extend(packages or self.name)\n        return self._exec(cmd)\n\n\ndef main():\n    arg_spec = dict(\n        executable=dict(default=None, type=\"path\"),\n        name=dict(required=True, type=\"list\", elements=\"str\"),\n        path=dict(default=None, type=\"path\"),\n        state=dict(default=\"present\", choices=[\"present\", \"absent\", \"latest\"]),\n        version=dict(default=None, type=\"str\"),\n        locked=dict(default=False, type=\"bool\"),\n    )\n    module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)\n\n    name = module.params[\"name\"]\n    state = module.params[\"state\"]\n    version = module.params[\"version\"]\n\n    if not name:\n        module.fail_json(msg=\"Package name must be specified\")\n\n    # Set LANG env since we parse stdout\n    module.run_command_environ_update = dict(\n        LANG=\"C\", LC_ALL=\"C\", LC_MESSAGES=\"C\", LC_CTYPE=\"C\"\n    )\n\n    cargo = Cargo(module, **module.params)\n    changed, out, err = False, None, None\n    installed_packages = cargo.get_installed()\n    if state == \"present\":\n        to_install = [\n            n\n            for n in name\n            if (n not in installed_packages)\n            or (version and version != installed_packages[n])\n        ]\n        if to_install:\n            changed = True\n            out, err = cargo.install(to_install)\n    elif state == \"latest\":\n        to_update = [\n            n for n in name if n not in installed_packages or cargo.is_outdated(n)\n        ]\n        if to_update:\n            changed = True\n            out, err = cargo.install(to_update)\n    else:  # absent\n        to_uninstall = [n for n in name if n in installed_packages]\n        if to_uninstall:\n            changed = True\n            out, err = cargo.uninstall(to_uninstall)\n\n    module.exit_json(changed=changed, stdout=out, stderr=err)\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "plugins/modules/cargo.py"}]}