{"id":24387157,"url":"https://github.com/gou177/recmd","last_synced_at":"2025-04-11T03:51:32.496Z","repository":{"id":245095882,"uuid":"793895805","full_name":"gou177/recmd","owner":"gou177","description":"Easy to use, type-safe (pyright), sync and async, and cross-platform command executor","archived":false,"fork":false,"pushed_at":"2025-03-18T19:53:16.000Z","size":20,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-18T20:37:04.342Z","etag":null,"topics":["shell"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gou177.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-30T04:32:51.000Z","updated_at":"2025-03-18T19:53:20.000Z","dependencies_parsed_at":"2024-06-19T20:09:00.260Z","dependency_job_id":"56b4d0e0-90a4-42de-851e-17557adc921b","html_url":"https://github.com/gou177/recmd","commit_stats":null,"previous_names":["gou177/recmd"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gou177%2Frecmd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gou177%2Frecmd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gou177%2Frecmd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gou177%2Frecmd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gou177","download_url":"https://codeload.github.com/gou177/recmd/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248339262,"owners_count":21087214,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["shell"],"created_at":"2025-01-19T12:22:10.329Z","updated_at":"2025-04-11T03:51:32.480Z","avatar_url":"https://github.com/gou177.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Recmd: Inspired by zx command constructor\n\nEasy to use, type-safe (pyright), sync and async, and cross-platform command executor\n\n## Installation\n\nInstall recmd using pip: `pip install recmd`\nWith async support: `pip install recmd[async]`\n\n## Usage\n\n#### Convert formatted strings to argument lists\n\n```py\nfrom recmd import shell, sh\n\n@sh\ndef argument_list(value: str, *args):\n    return shell(f\"executable arguments --value {value} 'more arguments with {value}' {args:*!s}\")\n    # :*!s converts args into `*[f\"{x!s}\" for x in args]`\n    # if !s is omitted then it turned into just `*args`\n    # you can also add any python format after :* (:*:.2f)\n  \nassert argument_list(\"test asd\", 1, 2, 3) == [\n    \"executable\", \"arguments\", \"--value\", \"test asd\", \"more arguments with test asd\", \"1\", \"2\", \"3\"\n]\n```\n\n#### Constructing commands\n\n```py\nimport sys\nfrom recmd.shell import sh\n\n@sh\ndef python(code: str, *args):\n    return sh(f\"{sys.executable} -c {code} {args:*}\")\n\n```\n\n#### Running commands\n\nSync:\n\n```py\nfrom recmd.executor.subprocess import SubprocessExecutor\n\n# set globally (context api)\nSubprocessExecutor.context.set(SubprocessExecutor())\n\n# set for code block\nwith SubprocessExecutor().use():\n    ...\n\n\n# `~` runs and waits for process to exit, then `assert` checks that exit code == 0\nassert ~python(\"pass\")\n\n# you can also use process as context manager\nwith python(\"pass\"):\n    pass\n\n```\n\nAsync:\n\n```py\nimport anyio\nfrom recmd.executor.anyio import AnyioExecutor\n\n# set globally (context api)\nAnyioExecutor.context.set(AnyioExecutor())\n\n# set for code block\nwith AnyioExecutor().use():\n    ...\n\nasync def run():\n    # `await` runs and waits for process to exit, then `assert` checks that exit code == 0\n    assert await python(\"pass\")\n\n    # you can also use process as context manager\n    async with python(\"pass\"):\n        pass\n\nanyio.run(run)\n```\n\n#### Interacting with processes\n\nSync:\n\n```py\n# send to stdin and read from stdout\nassert ~python(\"print(input(),end='')\").send(\"123\").output() == \"123\"\n\nfrom recmd import IOStream\n\n\n# manually control streams\nwith IOStream() \u003e\u003e python(\"print(input(),end='')\") \u003e\u003e IOStream() as process:\n    process.stdin.sync_io.write(b\"hello\")\n    process.stdin.sync_io.close()\n    assert process.stdout.sync_io.read() == b\"hello\"\n```\n\nAsync:\n\n```py\n# send to stdin and read from stdout\nassert await python(\"print(input(),end='')\").send(\"123\").output() == \"123\"\n\nfrom recmd import IOStream\n\n\n# manually control streams\nasync with IOStream() \u003e\u003e python(\"print(input(),end='')\") \u003e\u003e IOStream() as process:\n    await process.stdin.async_write.send(b\"hello\")\n    await process.stdin.async_write.aclose()\n    assert await process.stdout.async_read.receive() == \"bhello\"\n```\n\n#### Pipes\n\nSync:\n\n```py\n# redirect stdout from first process to stdin of second\nfrom recmd import Capture\n\ngroup = ~(python(\"print(123)\") | python(\"print(input(),end='')\") \u003e\u003e Capture())\n\nwith python(\"print(123)\") | python(\"print(input(),end='')\") \u003e\u003e Capture() as group:\n    ...\n\nassert group.commands[-1].stdout.get() == b\"123\"\n```\n\nAsync:\n\n```py\n# redirect stdout from first process to stdin of second\nfrom recmd import Capture\n\ngroup = await (python(\"print(123)\") | python(\"print(input(),end='')\") \u003e\u003e Capture())\n\nasync with python(\"print(123)\") | python(\"print(input(),end='')\") \u003e\u003e Capture() as group:\n    ...\n\nassert group.commands[-1].stdout.get() == b\"123\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgou177%2Frecmd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgou177%2Frecmd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgou177%2Frecmd/lists"}