Repository: xonsh/xonsh

Issue:
CommandPipeline behavior doesn't match tutorial claims
<details>
<summary><tt>$ xonfig</tt></summary>

```
+------------------+----------------------+
| xonsh            | 0.9.27               |
| Git SHA          | 71fe9014             |
| Commit Date      | Jan 29 08:58:58 2021 |
| Python           | 3.9.2                |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 3.0.16               |
| shell type       | prompt_toolkit       |
| pygments         | 2.8.0                |
| on posix         | True                 |
| on linux         | False                |
| on darwin        | True                 |
| on windows       | False                |
| on cygwin        | False                |
| on msys2         | False                |
| is superuser     | False                |
| default encoding | utf-8                |
| xonsh encoding   | utf-8                |
| encoding errors  | surrogateescape      |
| on jupyter       | False                |
| jupyter kernel   | None                 |
| xontrib          | []                   |
+------------------+----------------------+
```

</details>

The tutorial says this about CommandPipeline objects:
> This object will be “truthy” if its return code was 0, and it is equal (via ==) to its return code. It also hashes to its return code.

While the first claim checks out, the others don't seem to be correct:

```
$ r = !(ls nonexistent_directory)
$ r.returncode
ls: nonexistent_directory: No such file or directory
1
$ r == 1
False
$ hash(r)
283185562
```


Discussion:

Resolving patch:
diff --git a/xonsh/procs/pipelines.py b/xonsh/procs/pipelines.py
--- a/xonsh/procs/pipelines.py
+++ b/xonsh/procs/pipelines.py
@@ -184,9 +184,28 @@ def __repr__(self):
     def __bool__(self):
         return self.returncode == 0
 
+    def __int__(self):
+        return self.returncode
+
+    def __hash__(self):
+        return hash(self.returncode)
+
+    def __str__(self):
+        self.end()
+        return self.output
+
     def __len__(self):
         return len(self.procs)
 
+    def __eq__(self, other):
+        if isinstance(other, int):
+            return self.returncode == other
+        elif isinstance(other, str):
+            return str(self) == other
+        raise Exception(
+            f"CommandPipeline doesn't support comparing with {type(other)}."
+        )
+
     def __iter__(self):
         """Iterates through stdout and returns the lines, converting to
         strings and universal newlines if needed.


Test patch:
diff --git a/tests/test_pipelines.py b/tests/test_pipelines.py
--- a/tests/test_pipelines.py
+++ b/tests/test_pipelines.py
@@ -59,3 +59,21 @@ def test_simple_capture(cmdline, output, xonsh_execer):
 
 def test_raw_substitution(xonsh_execer):
     assert xonsh_execer.eval("$(echo @(b'bytes!'))") == "bytes!\n"
+
+
+@pytest.mark.parametrize("cmdline, result", (
+        ("bool(!(echo 1))", True),
+        ("bool(!(nocommand))", False),
+        ("int(!(echo 1))", 0),
+        ("int(!(nocommand))", 1),
+        ("hash(!(echo 1))", 0),
+        ("hash(!(nocommand))", 1),
+        ("str(!(echo 1))", '1\n'),
+        ("str(!(nocommand))", ''),
+        ("!(echo 1) == 0", True),
+        ("!(nocommand) == 1", True),
+        pytest.param("!(echo -n str) == 'str'", True, marks=skip_if_on_windows),
+        ("!(nocommand) == ''", True),
+))
+def test_casting(cmdline, result, xonsh_execer):
+    assert xonsh_execer.eval(f"{cmdline}") == result