{"id":15033138,"url":"https://github.com/redding/scmd","last_synced_at":"2025-08-24T21:12:45.601Z","repository":{"id":4319950,"uuid":"5454175","full_name":"redding/scmd","owner":"redding","description":"Build and run system commands.","archived":false,"fork":false,"pushed_at":"2021-10-29T19:05:43.000Z","size":76,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-03-15T03:55:11.017Z","etag":null,"topics":["ruby","system-commands"],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/redding.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}},"created_at":"2012-08-17T15:44:01.000Z","updated_at":"2023-11-02T08:52:55.000Z","dependencies_parsed_at":"2022-08-21T01:20:37.308Z","dependency_job_id":null,"html_url":"https://github.com/redding/scmd","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fscmd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fscmd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fscmd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fscmd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redding","download_url":"https://codeload.github.com/redding/scmd/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248113243,"owners_count":21049811,"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":["ruby","system-commands"],"created_at":"2024-09-24T20:20:12.815Z","updated_at":"2025-04-09T21:24:07.660Z","avatar_url":"https://github.com/redding.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Scmd\n\nBuild and run system commands.  Scmd uses `posix-spawn` to fork child processes to run the commands.\n\n## Usage\n\nCreate a command object:\n\n```ruby\ncmd = Scmd.new(\"echo hi\")\n\ncmd.to_s    #=\u003e \"echo hi\"\ncmd.inspect #=\u003e #\u003cScmd::Command:0x83220514 @cmd_str=\"echo hi\" @exitstatus=nil\u003e\n\ncmd.pid        #=\u003e nil\ncmd.exitstatus #=\u003e nil\ncmd.stdout     #=\u003e ''\ncmd.stderr     #=\u003e ''\n```\n\nRun it:\n\n```ruby\ncmd.run\n```\n\n**OR**, async run it:\n\n```ruby\ncmd.start\ncmd.running? # =\u003e true\ncmd.pid      #=\u003e 12345\n\n# do other stuff...\ncmd.wait # indefinitely until cmd exits\n```\n\n**OR**, async run it with a timeout:\n\n```ruby\ncmd.start\n\nbegin\n  cmd.wait(10)\nrescue Scmd::TimeoutError =\u003e err\n  cmd.stop # attempt to stop the cmd nicely, kill if doesn't stop in time\n  cmd.kill # just kill the cmd now\nend\n```\n\nResults:\n\n```ruby\n# written to the cmd instance\ncmd.pid        #=\u003e 12345\ncmd.exitstatus #=\u003e 0\ncmd.stdout     #=\u003e 'hi'\ncmd.stderr     #=\u003e ''\n\n# the cmd instance is returned by `run` for chaining as well\ncmd.run.stdout #=\u003e 'hi'\n```\n\n### Run with input on stdin\n\nA single input line\n\n```ruby\ninput = \"echo hi\"\ncmd = Scmd.new(\"sh\").run(input)\ncmd.stdout #=\u003e 'hi'\n```\n\nMultiple input lines:\n\n```ruby\ninput = [\"echo hi\", \"echo err 1\u003e\u00262\"]\ncmd = Scmd.new(\"sh\").run(input)\ncmd.stdout #=\u003e 'hi'\ncmd.stderr #=\u003e 'err'\n```\n\n### Some helpers\n\nAsk if cmd was successful:\n\n```ruby\nputs cmd.stderr if !cmd.success?\n```\n\nRaise an exception if not successful with `run!`:\n\n```ruby\nScmd.new(\"cd /path/that/does/not/exist\").run! #=\u003e Scmd::Command::Failure\n```\n\n### Environment variables\n\nPass environment variables:\n\n```ruby\ncmd = Scmd.new(\"echo $TEST_VAR\", {\n  :env =\u003e {\n    'TEST_VAR' =\u003e 'hi'\n  }\n})\n```\n\n### Process spawn options\n\nPass options:\n\n```ruby\nreader, writer = IO.pipe\n# this is an example that uses file descriptor redirection options\ncmd = Scmd.new(\"echo test 1\u003e\u0026#{writer.fileno}\", {\n  :options =\u003e { writer =\u003e writer }\n})\nreader.gets # =\u003e \"test\\n\"\n```\n\nFor all the possible options see [posix-spawn](https://github.com/rtomayko/posix-spawn#status).\n\n## Testing\n\nScmd comes with some testing utilities built in.  Specifically this includes a command spy and a \"test mode\" API on the main `Scmd` namespace.\n\n### Command Spy\n\n```ruby\nrequire 'scmd/command_spy'\nspy = Scmd::CommandSpy.new(cmd_str)\nspy.exitstatus = 1\nspy.stdout = 'some test output'\nAssert.stub(Scmd, :new).with(cmd_str){ spy }\n\ncmd = Scmd.new(cmd_str) # =\u003e spy\ncmd.run('some input')\n\ncmd.run_called?           # =\u003e true\ncmd.run_calls.size        # =\u003e 1\ncmd.run_calls.first.input # =\u003e 'some input'\n```\n\nThe spy is useful for stubbing out system commands that you don't want to call or aren't safe to call in the test suite.  It responds to the same API that commands do but doesn't run any system commands.\n\n### \"Test Mode\" API\n\n```ruby\nScmd.add_command(cmd_str){ |cmd| cmd.stdout = 'some output' } # =\u003e raises NoMethodError\n\nENV['SCMD_TEST_MODE'] = '1'\nScmd.add_command(cmd_str){ |cmd| cmd.stdout = 'some output' }\nScmd.add_command(cmd_str).with({:env =\u003e { :SOME_ENV_VAR =\u003e '1' }}) do |cmd|\n  cmd.stdout = 'some other output'\nend\nScmd.commands.empty? # =\u003e false\n\ncmd = Scmd.new(cmd_str)\ncmd.class                 # =\u003e Scmd::CommandSpy\ncmd.stdout                # =\u003e 'some output'\ncmd.run('some input')\nScmd.calls.size           # =\u003e 1\nScmd.calls.last.class     # =\u003e Scmd::Call\nScmd.calls.last.cmd_str   # =\u003e cmd_str\nScmd.calls.last.input     # =\u003e 'some input'\nScmd.calls.last.cmd.class # =\u003e Scmd::CommandSpy\n\ncmd = Scmd.new(cmd_str, {:env =\u003e { 'SOME_ENV_VAR' =\u003e '1' }})\ncmd.class                 # =\u003e Scmd::CommandSpy\ncmd.stdout                # =\u003e 'some other output'\ncmd.run('some input')\nScmd.calls.size           # =\u003e 2\nScmd.calls.last.class     # =\u003e Scmd::Call\nScmd.calls.last.cmd_str   # =\u003e cmd_str\nScmd.calls.last.input     # =\u003e 'some input'\nScmd.calls.last.cmd.class # =\u003e Scmd::CommandSpy\nScmd.calls.last.cmd.env   # =\u003e { 'SOME_ENV_VAR' =\u003e '1' }\n\nScmd.reset\nScmd.commands.empty? # =\u003e true\nScmd.calls.empty?    # =\u003e true\n```\n\nUse these singleton methods on the `Scmd` namespace to add specific command spies in specific contexts and to track command calls (runs, starts).  Use `reset` to reset the state of things.\n\n**Note:** these methods are only available when test mode is enabled (when the `SCMD_TEST_MODE` env var has a non-falsey value).  Otherwise these methods will raise `NoMethodError`.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'scmd'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install scmd\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Added some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredding%2Fscmd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredding%2Fscmd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredding%2Fscmd/lists"}