Spawn.js
[gitlive] / Spawn.js
1 ///<script type="text/javascript">
2
3 Gio      = imports.gi.Gio;
4 GLib      = imports.gi.GLib;
5
6
7 /**
8 * @namespace Spawn
9
10 * Library to wrap GLib.spawn_async_with_pipes
11
12 * usage:
13
14 * Spawn = import.Spawn;
15
16 * simple version..
17 * var output = Spawn.run({
18 *   cwd : '/home',
19 *   args : [ 'ls', '-l' ],
20 *   env : [], // optional
21 *   listeners : {
22         output : function (line) { Seed.print(line); },
23 *       stderr :  function (line) {Seed.print("ERROR" + line);  },
24 *       input : function() { return 'xxx' },
25 *   }
26 *  });
27
28
29 */
30
31
32 /**
33  * @class Spawn
34  * @param cfg {Object} settings - see properties.
35  * 
36  * @arg cwd {String}            working directory. (defaults to home directory)
37  * @arg args {Array}            arguments eg. [ 'ls', '-l' ]
38  * @arg listeners {Object} (optional) handlers for output, stderr, input
39  *     stderr/output both receive output line as argument
40  *     input should return any standard input
41  * @arg env {Array}             enviroment eg. [ 'GITDIR=/home/test' ]
42  * @arg async {Boolean} (optional)return instantly, or wait for exit. (default no)
43  * @arg exceptions {Boolean}    throw exception on failure (default no)
44  * 
45  * 
46  */
47 function Spawn(cfg) {
48     for(var i in cfg) {
49         this[i] = cfg[i];
50     }
51     // set defaults?
52     this.listeners = this.listeners || {};
53     this.cwd =  this.cwd || GLib.get_home_dir();
54     if (!this.args || !this.args.length) {
55         throw "No arguments";
56     }
57     
58 }
59 Spawn.prototype = {
60     
61     listeners : false,
62     async : false,
63     env : null,
64     cwd: false,
65     args: false,
66     exceptions : false,
67     /**
68      * @property output {String} resulting output
69      */
70     output  : '',
71     /**
72      * @property stderr {String} resulting output from stderr
73      */
74     stderr  : '',
75      /**
76      * @property result {Number} execution result.
77      */
78     result: 0
79     /**
80      * @property pid {Number} pid of child process (of false if it's not running)
81      */
82     pid : false,
83     
84     /**
85      * @property done {Boolean} has the process completed.
86      */
87     done : false,
88     
89     /**
90      * 
91      * @method run
92      * Run the configured command.
93      * 
94      */
95     
96     
97     run : function()
98     {
99         var ret = {};
100         GLib.spawn_async_with_pipes(this.cwd, this.args, null, 
101             GLib.SpawnFlags.DO_NOT_REAP_CHILD + GLib.SpawnFlags.SEARCH_PATH , 
102             null, null, ret);
103             
104         this.pid = ret.child_pid;
105         
106         var ctx = false; 
107        
108         var _this = this;
109         
110         GLib.child_watch_add(GLib.PRIORITY_DEFAULT, this.pid, function(pid, result) {
111             _this.result = result;
112             
113             GLib.spawn_close_pid(_this.pid);
114             _this.pid = false;
115             if (ctx) {
116                 GLib.main_loop_quit(ctx);
117             }
118             
119         });
120         this.in_ch = GLib.io_channel_unix_new(ret.standard_input);
121         var out_ch = GLib.io_channel_unix_new(ret.standard_output);
122         var err_ch = GLib.io_channel_unix_new(ret.standard_error);
123        
124         // make everything non-blocking!
125         GLib.io_channel_set_flags (this.in_ch,GLib.IOFlags.NONBLOCK);
126         GLib.io_channel_set_flags (out_ch,GLib.IOFlags.NONBLOCK);
127         GLib.io_channel_set_flags (err_ch,GLib.IOFlags.NONBLOCK);
128
129         function readstr(ch, prop) {
130             while (true) {
131                 var x = new GLib.String();
132                 var status = GLib.io_channel_read_line_string (ch, x);
133                 switch(status) {
134                     case GLib.IOStatus.NORMAL:
135                         //write(fn, x.str);
136                         if (this.listeners[prop]) {
137                             this.listeners[prop].call(this, x.str);
138                         }
139                         _this[prop] += x.str;
140                        continue;
141                     case GLib.IOStatus.AGAIN:   
142                         break;
143                     case GLib.IOStatus.ERROR:    
144                     case GLib.IOStatus.EOF:   
145                        break;
146                     
147                 }
148                 break;
149             }
150         }
151         
152         var out_src= GLib.io_add_watch(out_ch, GLib.PRIORITY_DEFAULT, 
153             GLib.IOCondition.OUT + GLib.IOCondition.IN  + GLib.IOCondition.PRI, function()
154         {
155             readstr(out_ch,  'output');
156             
157         });
158         var err_src= GLib.io_add_watch(err_ch, GLib.PRIORITY_DEFAULT, 
159             GLib.IOCondition.ERR + GLib.IOCondition.IN + GLib.IOCondition.PRI + GLib.IOCondition.OUT, 
160             function()
161         {
162              readstr(err_ch, 'stderr');
163              
164         });
165         if (this.pid !== false) {
166             // child can exit before we get this far..
167             if (this.listeners.input) {
168                 this.write(this.listeners.input.call(this));
169             }
170         }
171         if (this.pid !== false && !this.async) {
172             
173             ctx = GLib.main_loop_new (null, false);
174             GLib.main_loop_run(ctx, false); // wait fore exit?
175         }
176         // read any resulting data.
177         readstr(out_ch,  'output');
178         readstr(err_ch,  'error');
179         
180         // clean up.
181         
182         
183         GLib.io_channel_close(this.in_ch);
184         this.in_ch = false;
185         GLib.io_channel_close(out_ch);
186         GLib.io_channel_close(err_ch);
187         GLib.source_remove(err_src);
188         GLib.source_remove(out_src);
189         if (this.exceptions && this.result != 0) {
190             throw this.stderr;
191         }
192         return this;
193     
194     },
195     /**
196      * write to stdin of process
197      * @returns GLib.IOStatus (0 == error, 1= NORMAL)
198      * 
199      */
200     
201     write : function(str) // write a line to 
202     {
203         if (!this.in_ch) {
204             return; // input is closed
205         }
206         var ret = {};
207         var res = GLib.io_channel_write_chars(this.in_ch, str, str.length);
208         if (res != GLib.IOStatus.NORMAL) {
209             throw "Write failed";
210         }
211         return ret.bytes_written;
212         
213     }
214     
215 };
216 /**
217  * @function run 
218  * 
219  * simple run a process - returns result, or throws error..
220  * @param cfg {Object}  see spawn
221  */
222 function run(cfg) {
223     cfg.exceptions = true;
224     cfg.async = false;
225     var s = new Spawn(cfg);
226     var ret = s.run();
227     return ret.output;
228 }
229
230
231