Metadata-Version: 2.2
Name: recmd
Version: 0.1.0
Summary: Inspired by zx command constructor
Author-email: Ivan Vozhakov <gou177@bk.ru>
License: MIT License
        
        Copyright (c) 2024 rewirepy
        
        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
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/gou177/recmd
Keywords: command,shell,subprocess
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: async
Requires-Dist: asyncio; extra == "async"

# Recmd: Inspired by zx command constructor

Easy to use, type-safe (pyright), sync and async, and cross-platform command executor

## Installation

Install recmd using pip: `pip install recmd`
With async support: `pip install recmd[async]`

## Usage

#### Convert formatted strings to argument lists

```py
from recmd import shell, sh

@sh
def argument_list(value: str, *args):
    return shell(f"executable arguments --value {value} 'more arguments with {value}' {args:*!s}")
    # :*!s converts args into `*[f"{x!s}" for x in args]`
    # if !s is omitted then it turned into just `*args`
    # you can also add any python format after :* (:*:.2f)
  
assert argument_list("test asd", 1, 2, 3) == [
    "executable", "arguments", "--value", "test asd", "more arguments with test asd", "1", "2", "3"
]
```

#### Constructing commands

```py
import sys
from recmd.shell import sh

@sh
def python(code: str, *args):
    return sh(f"{sys.executable} -c {code} {args:*}")

```

#### Running commands

Sync:

```py
from recmd.executor.subprocess import SubprocessExecutor

# set globally (context api)
SubprocessExecutor.context.set(SubprocessExecutor())

# set for code block
with SubprocessExecutor().use():
    ...


# `~` runs and waits for process to exit, then `assert` checks that exit code == 0
assert ~python("pass")

# you can also use process as context manager
with python("pass"):
    pass

```

Async:

```py
import anyio
from recmd.executor.anyio import AnyioExecutor

# set globally (context api)
AnyioExecutor.context.set(AnyioExecutor())

# set for code block
with AnyioExecutor().use():
    ...

async def run():
    # `await` runs and waits for process to exit, then `assert` checks that exit code == 0
    assert await python("pass")

    # you can also use process as context manager
    async with python("pass"):
        pass

anyio.run(run)
```

#### Interacting with processes

Sync:

```py
# send to stdin and read from stdout
assert ~python("print(input(),end='')").send("123").output() == "123"

from recmd import IOStream


# manually control streams
with IOStream() >> python("print(input(),end='')") >> IOStream() as process:
    process.stdin.sync_io.write(b"hello")
    process.stdin.sync_io.close()
    assert process.stdout.sync_io.read() == b"hello"
```

Async:

```py
# send to stdin and read from stdout
assert await python("print(input(),end='')").send("123").output() == "123"

from recmd import IOStream


# manually control streams
async with IOStream() >> python("print(input(),end='')") >> IOStream() as process:
    await process.stdin.async_write.send(b"hello")
    await process.stdin.async_write.aclose()
    assert await process.stdout.async_read.receive() == "bhello"
```

#### Pipes

Sync:

```py
# redirect stdout from first process to stdin of second
from recmd import Capture

group = ~(python("print(123)") | python("print(input(),end='')") >> Capture())

with python("print(123)") | python("print(input(),end='')") >> Capture() as group:
    ...

assert group.commands[-1].stdout.get() == b"123"
```

Async:

```py
# redirect stdout from first process to stdin of second
from recmd import Capture

group = await (python("print(123)") | python("print(input(),end='')") >> Capture())

async with python("print(123)") | python("print(input(),end='')") >> Capture() as group:
    ...

assert group.commands[-1].stdout.get() == b"123"
```
