{"id":13508664,"url":"https://github.com/alco/porcelain","last_synced_at":"2025-05-15T02:07:28.020Z","repository":{"id":9830412,"uuid":"11818087","full_name":"alco/porcelain","owner":"alco","description":"Work with external processes like a boss","archived":false,"fork":false,"pushed_at":"2021-03-23T13:20:54.000Z","size":198,"stargazers_count":953,"open_issues_count":31,"forks_count":45,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-05-10T15:51:55.787Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://hexdocs.pm/porcelain","language":"Elixir","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/alco.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2013-08-01T13:50:57.000Z","updated_at":"2025-05-09T09:09:24.000Z","dependencies_parsed_at":"2022-08-09T12:10:16.839Z","dependency_job_id":null,"html_url":"https://github.com/alco/porcelain","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alco%2Fporcelain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alco%2Fporcelain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alco%2Fporcelain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alco%2Fporcelain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alco","download_url":"https://codeload.github.com/alco/porcelain/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254259383,"owners_count":22040820,"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":[],"created_at":"2024-08-01T02:00:56.495Z","updated_at":"2025-05-15T02:07:27.977Z","avatar_url":"https://github.com/alco.png","language":"Elixir","funding_links":[],"categories":["Miscellaneous"],"sub_categories":[],"readme":"Porcelain\n=========\n\n[![Build status](https://travis-ci.org/alco/porcelain.svg \"Build status\")](https://travis-ci.org/alco/porcelain)\n[![Hex version](https://img.shields.io/hexpm/v/porcelain.svg \"Hex version\")](https://hex.pm/packages/porcelain)\n![Hex downloads](https://img.shields.io/hexpm/dt/porcelain.svg \"Hex downloads\")\n\nPorcelain implements a saner approach to launching and communicating with\nexternal OS processes from Elixir. Built on top of Erlang's ports, it provides\nricher functionality and simpler API.\n\nSimply put, Porcelain removes the pain of dealing with ports and substitutes it\nwith happiness and peace of mind.\n\n\n## Overview\n\nHaving some 20 odd options, the Erlang port API can be unwieldy and cumbersome\nto use. Porcelain replaces it with a simpler approach and provides defaults for\nthe common cases.\n\nUser-level features include:\n\n  * sane API\n\n  * ability to launch external programs in a synchronous or asynchronous manner\n\n  * multiple ways of passing input to the program and getting back its output\n    (including working directly with files and Elixir streams)\n\n  * being able to work with programs that try to read the whole input until EOF\n    before producing output\n\n  * ability to send OS signals to external processes (requires goon v2.0)\n\nTo read background story on the library's design and possible future\nextensions, please refer to the [wiki][].\n\n  [wiki]: https://github.com/alco/porcelain/wiki\n\n\n## Installation\n\nAdd Porcelain as a dependency to your Mix project:\n\n```elixir\ndef application do\n  [applications: [:porcelain]]\nend\n\ndefp deps do\n  [{:porcelain, \"~\u003e 2.0\"}]\nend\n```\n\nNow, some of the advanced functionality is provided by the external program\ncalled `goon`. See which particular features it implements in the reference\ndocs [here][goon_ref]. Go to `goon`'s [project page][goon] to find out how to\ninstall it.\n\n  [goon_ref]: http://hexdocs.pm/porcelain/Porcelain.Driver.Goon.html\n  [goon]: https://github.com/alco/goon#goon\n\n\n## Usage\n\nExamples below show some of the common use cases. See also this [demo\napp][exapp]. Refer to the [API docs][ref] to familiarize yourself with the\ncomplete set of provided functions and options.\n\n\n### Launching one-off programs\n\nIf you need to launch an external program, feed it some input and capture its\noutput and maybe also exit status, use `exec()` or `shell()`:\n\n```elixir\nalias Porcelain.Result\n\n%Result{out: output, status: status} = Porcelain.shell(\"date\")\nIO.inspect status   #=\u003e 0\nIO.inspect output   #=\u003e \"Fri Jun  6 14:12:02 EEST 2014\\n\"\n\nresult = Porcelain.shell(\"date | cut -b 1-3\")\nIO.inspect result.out   #=\u003e \"Fri\\n\"\n\n# Use exec() when you want launch a program directly without using a shell\nFile.write!(\"input.txt\", \"lines\\nread\\nfrom\\nfile\\n\")\nresult = Porcelain.exec(\"sort\", [\"input.txt\"])\nIO.inspect result.out   #=\u003e \"file\\nfrom\\nlines\\nread\\n\"\n```\n\n\n### Passing input and getting output\n\nPorcelain gives you many options when it comes to interacting with external\nprocesses. It is possible to feed input from a file or a stream, same for\noutput:\n\n```elixir\nFile.write!(\"input.txt\", \"\"\"\n  This file contains some patterns\n  \u003elike this\u003c\n  interspersed with other text\n  ... \u003elike this\u003c the end.\n  \"\"\")\n\nPorcelain.exec(\"grep\", [\"\u003elike this\u003c\", \"-m\", \"2\"],\n                    in: {:path, \"input.txt\"}, out: {:append, \"output.txt\"})\nIO.inspect File.read!(\"output.txt\")\n#=\u003e \"\u003elike this\u003c\\n... \u003elike this\u003c the end.\\n\"\n```\n\n\n### Streams\n\nPrograms can be spawned asynchronously (using `spawn()` and `spawn_shell()`)\nallowing for continuously exchanging data between Elixir and the external\nprocess.\n\nIn the next example we will use streams for both input and output.\n\n```elixir\nalias Porcelain.Process, as: Proc\n\ninstream = SocketStream.new('example.com', 80)\nopts = [in: instream, out: :stream]\nproc = %Proc{out: outstream} = Porcelain.spawn(\"grep\", [\"div\", \"-m\", \"4\"], opts)\n\nEnum.into(outstream, IO.stream(:stdio, :line))\n#     div {\n#         div {\n# \u003cdiv\u003e\n# \u003c/div\u003e\n\nProc.alive?(proc)   #=\u003e false\n```\n\nAlternatively, we could pass the output stream directly to the call to\n`spawn()`:\n\n```elixir\nopts = [\n  in: SocketStream.new('example.com', 80),\n  out: IO.stream(:stderr, :line),\n]\nPorcelain.exec(\"grep\", [\"div\", \"-m\", \"4\"], opts)\n#=\u003e this will be printed to stderr of the running Elixir process:\n#     div {\n#         div {\n# \u003cdiv\u003e\n# \u003c/div\u003e\n```\n\nThe `SocketStream` module used above wraps a tcp socket in a stream. Its\nimplementation can be found in the `test/util/socket_stream.exs` file.\n\n\n### Messages\n\nIf you prefer to exchange messages with the external process, you can do that:\n\n```elixir\nalias Porcelain.Process, as: Proc\nalias Porcelain.Result\n\nproc = %Proc{pid: pid} =\n  Porcelain.spawn_shell(\"grep ohai -m 2 --line-buffered\",\n                                in: :receive, out: {:send, self()})\n\nProc.send_input(proc, \"ohai proc\\n\")\nreceive do\n  {^pid, :data, :out, data} -\u003e IO.inspect data   #=\u003e \"ohai proc\\n\"\nend\n\nProc.send_input(proc, \"this won't match\\n\")\nProc.send_input(proc, \"ohai\")\nProc.send_input(proc, \"\\n\")\nreceive do\n  {^pid, :data, :out, data} -\u003e IO.inspect data   #=\u003e \"ohai\\n\"\nend\nreceive do\n  {^pid, :result, %Result{status: status}} -\u003e IO.inspect status   #=\u003e 0\nend\n```\n\n\n## Configuring the Goon driver\n\nThere are a number of options you can tweak to customize the way `goon` is\nused. All of the options described below should be put into your `config.exs`\nfile.\n\n\n### Setting the driver\n\n```elixir\nconfig :porcelain, :driver, \u003cdriver\u003e\n```\n\nThis option allows you to set a particular driver to be used at all times.\n\nBy default, Porcelain will try to detect the `goon` executable. If it can find\none, it will use `Porcelain.Driver.Goon`. Otherwise, it will print a warning to\nstderr and fall back to `Porcelain.Driver.Basic`.\n\nBy setting `Porcelain.Driver.Basic` above you can force Porcelain to always\nuse the basic driver.\n\nIf you set `Porcelain.Driver.Goon`, Porcelain will always use the Goon driver\nand will fail to start if the `goon` executable can't be found.\n\n\n### Goon options\n\n```elixir\nconfig :porcelain, :goon_driver_path, \u003cpath\u003e\n```\n\nSet an absolute path to the `goon` executable. If this is not set, Porcelain\nwill search your system's `PATH` by default.\n\n```elixir\nconfig :porcelain, :goon_stop_timeout, \u003cinteger\u003e\n```\n\nThis setting is used by `Porcelain.Process.stop/1`. It specifes the number of seconds `goon` will\nwait for the external process to terminate before it sends `SIGKILL` to it. Default timeout is 10\nseconds.\n\n```elixir\nconfig :porcelain, :goon_warn_if_missing, \u003cboolean\u003e\n```\n\nPrint a warning to the console if the `goon` executable isn't found. Default: `true`.\n\n\n## Going deeper\n\nTake a look at the [reference docs][ref] for the full description of all\nprovided functions and supported options.\n\n  [exapp]: https://github.com/alco/porcelain_example\n  [ref]: http://hexdocs.pm/porcelain/api-reference.html\n\n\n## Known issues and roadmap\n\n  * there are known crashes happening when using Porcelain across two nodes\n  * error handling when using the Goon driver is not completely shaped out\n\n\n## Acknowledgements\n\nHuge thanks to all who have been test-driving the library in production, in\nparticular to\n\n  * Josh Adams\n  * Tim Ruffles\n\n\n## License\n\nThis software is licensed under [the MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falco%2Fporcelain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falco%2Fporcelain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falco%2Fporcelain/lists"}