├── .travis.yml ├── .travis_build_config.rb ├── LICENSE ├── README.md ├── mrbgem.rake ├── mrblib └── mrb_open3.rb └── src ├── mrb_open3.c └── mrb_open3.h /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | branches: 3 | only: 4 | - master 5 | compiler: 6 | - gcc 7 | - clang 8 | before_install: 9 | - sudo apt-get -qq update 10 | install: 11 | - sudo apt-get -qq install rake bison git gperf 12 | before_script: 13 | - cd ../ 14 | - git clone https://github.com/mruby/mruby.git 15 | - cd mruby 16 | - cp -fp ../mruby-open3/.travis_build_config.rb build_config.rb 17 | script: 18 | - make 19 | -------------------------------------------------------------------------------- /.travis_build_config.rb: -------------------------------------------------------------------------------- 1 | MRuby::Build.new do |conf| 2 | toolchain :gcc 3 | conf.gembox 'default' 4 | conf.gem '../mruby-open3' 5 | end 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | mruby-open3 2 | 3 | Copyright (c) Takashi Kokubun 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | [ MIT license: http://www.opensource.org/licenses/mit-license.php ] 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mruby-open3 [![Build Status](https://travis-ci.org/mrbgems/mruby-open3.svg?branch=master)](https://travis-ci.org/mrbgems/mruby-open3) 2 | 3 | Open3 for mruby 4 | 5 | ## Installation 6 | 7 | ```ruby 8 | MRuby::Build.new do |conf| 9 | # ... 10 | conf.gem mgem: 'mruby-open3' 11 | end 12 | ``` 13 | 14 | ## Usage 15 | 16 | Currently only `Open3.capture3` is supported. 17 | 18 | ```rb 19 | Open3.capture3("ruby", "-e", "$stdout.puts 'out'; $stderr.puts 'err'; exit 2") 20 | #=> ["out\n", "err\n", #] 21 | ``` 22 | 23 | ## License 24 | 25 | MIT License 26 | -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | MRuby::Gem::Specification.new('mruby-open3') do |spec| 2 | spec.license = 'MIT' 3 | spec.authors = 'Takashi Kokubun' 4 | spec.add_dependency 'mruby-io' 5 | spec.add_dependency 'mruby-process' 6 | spec.add_dependency 'mruby-string-ext', core: 'mruby-string-ext' 7 | end 8 | -------------------------------------------------------------------------------- /mrblib/mrb_open3.rb: -------------------------------------------------------------------------------- 1 | module Open3 2 | # @param [Array] - command to execute 3 | # @return [String, String, Process::Status] - stdout, status 4 | def capture2(*cmd) 5 | stdout, stderr, status = capture3(*cmd) 6 | $stderr.print(stderr) 7 | [stdout, status] 8 | end 9 | module_function :capture2 10 | 11 | # @param [Array] - command to execute 12 | # @return [String, String, Process::Status] - stdout_and_stderr_str, status 13 | def capture2e(*cmd) 14 | opts = {} 15 | if cmd.last.is_a?(Hash) 16 | opts = cmd.pop.dup 17 | end 18 | out_r, out_w = IO.pipe 19 | opts[:out] = out_w.to_i 20 | opts[:err] = out_w.to_i 21 | pid = spawn(*cmd, opts) 22 | 23 | out_w.close 24 | 25 | stdout_and_stderr_str = '' 26 | 27 | remaining_ios = [out_r] 28 | buf = '' 29 | until remaining_ios.empty? 30 | readable_ios, = IO.select(remaining_ios) 31 | readable_ios.each do |io| 32 | begin 33 | io.sysread(1024, buf) 34 | stdout_and_stderr_str << buf 35 | rescue EOFError 36 | io.close unless io.closed? 37 | remaining_ios.delete(io) 38 | end 39 | end 40 | end 41 | 42 | _, status = Process.waitpid2(pid) 43 | [stdout_and_stderr_str, status] 44 | end 45 | module_function :capture2e 46 | 47 | # @param [Array] - command to execute 48 | # @return [String, String, Process::Status] - stdout, stderr, status 49 | def capture3(*cmd) 50 | opts = {} 51 | if cmd.last.is_a?(Hash) 52 | opts = cmd.pop.dup 53 | end 54 | out_r, out_w = IO.pipe 55 | err_r, err_w = IO.pipe 56 | opts[:out] = out_w.to_i 57 | opts[:err] = err_w.to_i 58 | pid = spawn(*cmd, opts) 59 | 60 | out_w.close 61 | err_w.close 62 | 63 | stdout = '' 64 | stderr = '' 65 | 66 | remaining_ios = [out_r, err_r] 67 | buf = '' 68 | until remaining_ios.empty? 69 | readable_ios, = IO.select(remaining_ios) 70 | readable_ios.each do |io| 71 | begin 72 | io.sysread(1024, buf) 73 | if io == out_r 74 | stdout << buf 75 | else 76 | stderr << buf 77 | end 78 | rescue EOFError 79 | io.close unless io.closed? 80 | remaining_ios.delete(io) 81 | end 82 | end 83 | end 84 | 85 | _, status = Process.waitpid2(pid) 86 | [stdout, stderr, status] 87 | end 88 | module_function :capture3 89 | end 90 | -------------------------------------------------------------------------------- /src/mrb_open3.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** mrb_open3.c - Open3 module 3 | ** 4 | ** Copyright (c) Takashi Kokubun 2016 5 | ** 6 | ** See Copyright Notice in LICENSE 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "mruby.h" 14 | #include "mruby/hash.h" 15 | #include "mruby/string.h" 16 | #include "mrb_open3.h" 17 | 18 | #define DONE mrb_gc_arena_restore(mrb, 0); 19 | 20 | struct spawn_options { 21 | mrb_int out_dst; 22 | mrb_int err_dst; 23 | char *chdir; 24 | }; 25 | 26 | static void 27 | open3_spawn_process_options(mrb_state *mrb, mrb_value options_value, struct spawn_options *options) 28 | { 29 | mrb_value out_value, err_value, chdir_value; 30 | 31 | out_value = mrb_hash_get(mrb, options_value, mrb_symbol_value(mrb_intern_lit(mrb, "out"))); 32 | err_value = mrb_hash_get(mrb, options_value, mrb_symbol_value(mrb_intern_lit(mrb, "err"))); 33 | chdir_value = mrb_hash_get(mrb, options_value, mrb_symbol_value(mrb_intern_lit(mrb, "chdir"))); 34 | options->out_dst = mrb_int(mrb, out_value); 35 | options->err_dst = mrb_int(mrb, err_value); 36 | if (!mrb_nil_p(chdir_value)) { 37 | options->chdir = mrb_str_to_cstr(mrb, chdir_value); 38 | } else { 39 | options->chdir = NULL; 40 | } 41 | } 42 | 43 | static char ** 44 | mrb_str_buf_to_cstr_buf(mrb_state *mrb, mrb_value *strs, mrb_int num) 45 | { 46 | char **ret; 47 | mrb_int i; 48 | 49 | ret = (char **)mrb_malloc(mrb, sizeof(char *) * (num+1)); 50 | for (i = 0; i < num; i++) { 51 | ret[i] = mrb_str_to_cstr(mrb, strs[i]); 52 | } 53 | ret[num] = NULL; 54 | return ret; 55 | } 56 | 57 | // `spawn` is defined under `Open3` since it's incomplete and it should be added 58 | // to `Process` by "mruby-process" mrbgem. 59 | // 60 | // Limitation: Currently it expects the last argument to be a Hash like `{ out: ..., err: ... }`. 61 | static mrb_value 62 | mrb_open3_spawn(mrb_state *mrb, mrb_value self) 63 | { 64 | char **cmd; 65 | pid_t pid; 66 | mrb_value *argv; 67 | mrb_int argc; 68 | struct spawn_options options; 69 | 70 | mrb_get_args(mrb, "*", &argv, &argc); 71 | if (argc == 0) { 72 | mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given 0, expected 1+)"); 73 | } 74 | 75 | cmd = mrb_str_buf_to_cstr_buf(mrb, argv, argc-1); 76 | open3_spawn_process_options(mrb, argv[argc-1], &options); 77 | 78 | pid = fork(); 79 | if (pid == 0) { 80 | dup2(options.out_dst, STDOUT_FILENO); 81 | dup2(options.err_dst, STDERR_FILENO); 82 | if (options.chdir != NULL) { 83 | chdir(options.chdir); 84 | } 85 | execvp(cmd[0], cmd); 86 | 87 | // execvp does not return on success. 88 | fprintf(stderr, "execvp(%s): %s", cmd[0], strerror(errno)); 89 | _exit(EXIT_FAILURE); 90 | } 91 | return mrb_fixnum_value(pid); 92 | } 93 | 94 | void 95 | mrb_mruby_open3_gem_init(mrb_state *mrb) 96 | { 97 | struct RClass *open3; 98 | open3 = mrb_define_module(mrb, "Open3"); 99 | mrb_define_method(mrb, open3, "spawn", mrb_open3_spawn, MRB_ARGS_REQ(1) | MRB_ARGS_REST()); 100 | mrb_define_class_method(mrb, open3, "spawn", mrb_open3_spawn, MRB_ARGS_REQ(1) | MRB_ARGS_REST()); 101 | DONE; 102 | } 103 | 104 | void 105 | mrb_mruby_open3_gem_final(mrb_state *mrb) 106 | { 107 | } 108 | -------------------------------------------------------------------------------- /src/mrb_open3.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** mrb_open3.h - Open3 class 3 | ** 4 | ** See Copyright Notice in LICENSE 5 | */ 6 | 7 | #ifndef MRB_OPEN3_H 8 | #define MRB_OPEN3_H 9 | 10 | void mrb_mruby_open3_gem_init(mrb_state *mrb); 11 | 12 | #endif 13 | --------------------------------------------------------------------------------