• Stars
    star
    917
  • Rank 47,719 (Top 1.0 %)
  • Language
    Elixir
  • License
    MIT License
  • Created over 10 years ago
  • Updated about 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Work with external processes like a boss

Porcelain

Build status Hex version Hex downloads

Porcelain implements a saner approach to launching and communicating with external OS processes from Elixir. Built on top of Erlang's ports, it provides richer functionality and simpler API.

Simply put, Porcelain removes the pain of dealing with ports and substitutes it with happiness and peace of mind.

Overview

Having some 20 odd options, the Erlang port API can be unwieldy and cumbersome to use. Porcelain replaces it with a simpler approach and provides defaults for the common cases.

User-level features include:

  • sane API

  • ability to launch external programs in a synchronous or asynchronous manner

  • multiple ways of passing input to the program and getting back its output (including working directly with files and Elixir streams)

  • being able to work with programs that try to read the whole input until EOF before producing output

  • ability to send OS signals to external processes (requires goon v2.0)

To read background story on the library's design and possible future extensions, please refer to the wiki.

Installation

Add Porcelain as a dependency to your Mix project:

def application do
  [applications: [:porcelain]]
end

defp deps do
  [{:porcelain, "~> 2.0"}]
end

Now, some of the advanced functionality is provided by the external program called goon. See which particular features it implements in the reference docs here. Go to goon's project page to find out how to install it.

Usage

Examples below show some of the common use cases. See also this demo app. Refer to the API docs to familiarize yourself with the complete set of provided functions and options.

Launching one-off programs

If you need to launch an external program, feed it some input and capture its output and maybe also exit status, use exec() or shell():

alias Porcelain.Result

%Result{out: output, status: status} = Porcelain.shell("date")
IO.inspect status   #=> 0
IO.inspect output   #=> "Fri Jun  6 14:12:02 EEST 2014\n"

result = Porcelain.shell("date | cut -b 1-3")
IO.inspect result.out   #=> "Fri\n"

# Use exec() when you want launch a program directly without using a shell
File.write!("input.txt", "lines\nread\nfrom\nfile\n")
result = Porcelain.exec("sort", ["input.txt"])
IO.inspect result.out   #=> "file\nfrom\nlines\nread\n"

Passing input and getting output

Porcelain gives you many options when it comes to interacting with external processes. It is possible to feed input from a file or a stream, same for output:

File.write!("input.txt", """
  This file contains some patterns
  >like this<
  interspersed with other text
  ... >like this< the end.
  """)

Porcelain.exec("grep", [">like this<", "-m", "2"],
                    in: {:path, "input.txt"}, out: {:append, "output.txt"})
IO.inspect File.read!("output.txt")
#=> ">like this<\n... >like this< the end.\n"

Streams

Programs can be spawned asynchronously (using spawn() and spawn_shell()) allowing for continuously exchanging data between Elixir and the external process.

In the next example we will use streams for both input and output.

alias Porcelain.Process, as: Proc

instream = SocketStream.new('example.com', 80)
opts = [in: instream, out: :stream]
proc = %Proc{out: outstream} = Porcelain.spawn("grep", ["div", "-m", "4"], opts)

Enum.into(outstream, IO.stream(:stdio, :line))
#     div {
#         div {
# <div>
# </div>

Proc.alive?(proc)   #=> false

Alternatively, we could pass the output stream directly to the call to spawn():

opts = [
  in: SocketStream.new('example.com', 80),
  out: IO.stream(:stderr, :line),
]
Porcelain.exec("grep", ["div", "-m", "4"], opts)
#=> this will be printed to stderr of the running Elixir process:
#     div {
#         div {
# <div>
# </div>

The SocketStream module used above wraps a tcp socket in a stream. Its implementation can be found in the test/util/socket_stream.exs file.

Messages

If you prefer to exchange messages with the external process, you can do that:

alias Porcelain.Process, as: Proc
alias Porcelain.Result

proc = %Proc{pid: pid} =
  Porcelain.spawn_shell("grep ohai -m 2 --line-buffered",
                                in: :receive, out: {:send, self()})

Proc.send_input(proc, "ohai proc\n")
receive do
  {^pid, :data, :out, data} -> IO.inspect data   #=> "ohai proc\n"
end

Proc.send_input(proc, "this won't match\n")
Proc.send_input(proc, "ohai")
Proc.send_input(proc, "\n")
receive do
  {^pid, :data, :out, data} -> IO.inspect data   #=> "ohai\n"
end
receive do
  {^pid, :result, %Result{status: status}} -> IO.inspect status   #=> 0
end

Configuring the Goon driver

There are a number of options you can tweak to customize the way goon is used. All of the options described below should be put into your config.exs file.

Setting the driver

config :porcelain, :driver, <driver>

This option allows you to set a particular driver to be used at all times.

By default, Porcelain will try to detect the goon executable. If it can find one, it will use Porcelain.Driver.Goon. Otherwise, it will print a warning to stderr and fall back to Porcelain.Driver.Basic.

By setting Porcelain.Driver.Basic above you can force Porcelain to always use the basic driver.

If you set Porcelain.Driver.Goon, Porcelain will always use the Goon driver and will fail to start if the goon executable can't be found.

Goon options

config :porcelain, :goon_driver_path, <path>

Set an absolute path to the goon executable. If this is not set, Porcelain will search your system's PATH by default.

config :porcelain, :goon_stop_timeout, <integer>

This setting is used by Porcelain.Process.stop/1. It specifes the number of seconds goon will wait for the external process to terminate before it sends SIGKILL to it. Default timeout is 10 seconds.

config :porcelain, :goon_warn_if_missing, <boolean>

Print a warning to the console if the goon executable isn't found. Default: true.

Going deeper

Take a look at the reference docs for the full description of all provided functions and supported options.

Known issues and roadmap

  • there are known crashes happening when using Porcelain across two nodes
  • error handling when using the Goon driver is not completely shaped out

Acknowledgements

Huge thanks to all who have been test-driving the library in production, in particular to

  • Josh Adams
  • Tim Ruffles

License

This software is licensed under the MIT license.

More Repositories

1

gostart

A getting started guide for Go newcomers
1,827
star
2

benchfella

Microbenchmarking tool for Elixir
Elixir
510
star
3

hashids-elixir

Stringify your ids
Elixir
270
star
4

psdump

Extract layout from Photoshop files into one of several text-based formats.
C
85
star
5

goon

Middleman implementation for Porcelain
Go
53
star
6

chatty

Basic IRC client for writing bots
Elixir
35
star
7

pipespect

Auto-inspect for pipes
Elixir
29
star
8

ExReminder

A simple reminder app written in Elixir
Elixir
25
star
9

elixir-datetime

Elixir
23
star
10

erlang-mix-project

A sample Erlang project that uses Mix
Erlang
19
star
11

phoenix-docker-example

A demo showing how to run a Phoenix app within a docker container
Elixir
19
star
12

mix-erlang-tasks

Common tasks for Erlang projects that use Mix
Elixir
18
star
13

EGL_mac_ios

A port of the EGL API to Mac OS X (OpenGL) and iOS (OpenGL ES 1 & 2)
Objective-C
12
star
14

beamie_bot

Find beamie on #elixir-lang on irc.freenode.net
Elixir
11
star
15

commando

Advanced command-line argument parsing
Elixir
10
star
16

muweb

A minimalistic web framework
Elixir
9
star
17

miniweb

Web-related utilities
Elixir
8
star
18

TastyKVO

A simpler key-value observing API (with blocks!)
Objective-C
7
star
19

numspell

A simple python module for number spelling
Python
6
star
20

go_pil

Go
6
star
21

blog

My blog
Shell
5
star
22

gochan

Implementation of Go channels in Elixir
Elixir
5
star
23

cloaked-octo-robot

Elixir
4
star
24

EGL_samples

Sample apps for the EGL_mac_ios port
Objective-C
4
star
25

porcelain_example

Example project showing how to use Porcelain
Elixir
4
star
26

dayton-talk-cli-apps

Slides and code for my Dayton talk about command-line apps in Elixir
Elixir
3
star
27

wyvern

Versatile view engine for Elixir
Elixir
3
star
28

Remotely

A set of Objective-C classes to simplify downloading/uploading/syncing files and directories
Objective-C
2
star
29

objc.string

Self-contained library for efficient string manipulation in Objective-C.
Objective-C
2
star
30

mix-run

A sample project showing how to teach Mix to behave
Elixir
2
star
31

rust-digest

Implementations of missing hash/digest/checksum algorithms for Rust
Rust
2
star
32

simpleweb

A simple web framework in Elixir
Elixir
2
star
33

2048-elixir

Sliding tile puzzle game implemented in Elixir.
Elixir
2
star
34

french-cards

Flash cards for studying French
Python
2
star
35

ace-mode-elixir

Elixir mode for Ace editor
JavaScript
1
star
36

elixir-datefmt

Elixir
1
star
37

99-lisp-problems

My solutions
Clojure
1
star
38

Numerissimo

Client-side web app for aspired foreign language learners
JavaScript
1
star
39

bingo

Parse binary data in Go like a boss
Go
1
star
40

elixir-docs

The Unofficial Elixir Documentation
Python
1
star
41

exdoc_sphinx_formatter

Custom formatter for ExDoc
Elixir
1
star
42

ansi-progress

Elixir
1
star
43

PhotoJS

A collection of scripts for automating common gamedev related tasks in Photoshop
JavaScript
1
star
44

3d

3d
JavaScript
1
star
45

perseus

Perseus persist HTML nodes, form fields and your custom objects
JavaScript
1
star
46

Funjective

Functional programming with blocks for Objective-C
Objective-C
1
star
47

parallax

A rudimentary parallax test
JavaScript
1
star
48

requestanimationframedemo

JavaScript
1
star
49

wyvern-examples

WIP, check back later
Elixir
1
star