PageRenderTime 42ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/scala/cc/factorie/util/Pipes.scala

https://code.google.com/p/factorie/
Scala | 498 lines | 255 code | 84 blank | 159 comment | 42 complexity | f8d94b0367ff9a25f6aded2e5acc6950 MD5 | raw file
Possible License(s): Apache-2.0
  1. /* Copyright (C) 2008-2010 University of Massachusetts Amherst,
  2. Department of Computer Science.
  3. This file is part of "FACTORIE" (Factor graphs, Imperative, Extensible)
  4. http://factorie.cs.umass.edu, http://code.google.com/p/factorie/
  5. Licensed under the Apache License, Version 2.0 (the "License");
  6. you may not use this file except in compliance with the License.
  7. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License. */
  14. package cc.factorie.util
  15. /*
  16. Copyright 2009 David Hall, Daniel Ramage
  17. Licensed under the Apache License, Version 2.0 (the "License");
  18. you may not use this file except in compliance with the License.
  19. You may obtain a copy of the License at
  20. http://www.apache.org/licenses/LICENSE-2.0
  21. Unless required by applicable law or agreed to in writing, software
  22. distributed under the License is distributed on an "AS IS" BASIS,
  23. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24. See the License for the specific language governing permissions and
  25. limitations under the License.
  26. */
  27. import java.io.File
  28. import java.io.InputStream
  29. import java.io.OutputStream
  30. import java.lang.Process
  31. import java.lang.ProcessBuilder
  32. import scala.concurrent.ops._
  33. /**
  34. * Utilities for executing shell scripts and reading from file in
  35. * a similar way to unix shell piping. To get started with a global
  36. * pipes shell, use:
  37. *
  38. * import scalanlp.util.Pipes.global._
  39. *
  40. * and see examples in the main method.
  41. *
  42. * @author dramage
  43. */
  44. class Pipes {
  45. // Pipes instance for auto-importing into called contexts.
  46. implicit val pipes = this;
  47. //
  48. // state variables
  49. //
  50. protected var _cwd: File = new File(new File("").getAbsolutePath);
  51. protected var _stdout: OutputStream = java.lang.System.out;
  52. protected var _stderr: OutputStream = java.lang.System.err;
  53. protected var _stdin: InputStream = java.lang.System.in;
  54. //
  55. // context properties
  56. //
  57. /**Returns the default stdout used in this context. */
  58. def stdout = _stdout;
  59. /**Sets the default stdout used in this context. */
  60. def stdout(stream: OutputStream): Unit = _stdout = stream;
  61. /**Returns the default stderr used in this context. */
  62. def stderr = _stderr;
  63. /**Sets the default stderr used in this context. */
  64. def stderr(stream: OutputStream): Unit = _stderr = stream;
  65. /**Returns the default stdin used in this context. */
  66. def stdin = _stdin;
  67. /**Sets the default stdin used in this context. */
  68. def stderr(stream: InputStream): Unit = _stdin = stream;
  69. /**Returns a copy of the pipes context. */
  70. def copy: Pipes = {
  71. val _pipes = new Pipes;
  72. _pipes._cwd = _cwd;
  73. _pipes._stdout = _stdout;
  74. _pipes._stderr = _stderr;
  75. _pipes._stdin = _stdin;
  76. return _pipes;
  77. }
  78. /**
  79. * Runs the given command (via the system command shell if found)
  80. * in the current directory.
  81. */
  82. def sh(command: String): java.lang.Process = {
  83. val os = System.getProperty("os.name");
  84. val pb = new ProcessBuilder().directory(_cwd);
  85. if (os == "Windows 95" || os == "Windows 98" || os == "Windows ME") {
  86. pb.command("command.exe", "/C", command);
  87. } else if (os.startsWith("Windows")) {
  88. pb.command("cmd.exe", "/C", command);
  89. } else {
  90. pb.command("/bin/sh", "-c", command);
  91. };
  92. return pb.start();
  93. }
  94. /**
  95. * Returns the current working directory.
  96. */
  97. def cwd: File = _cwd;
  98. /**
  99. * Changes to the given directory.
  100. */
  101. def cd(folder: File) = {
  102. if (!folder.exists) {
  103. error("Folder " + folder + " does not exist.");
  104. } else if (!folder.isDirectory) {
  105. error("Folder " + folder + " is not a directory");
  106. } else if (!folder.canRead) {
  107. error("Cannot access folder " + folder);
  108. }
  109. _cwd = folder;
  110. }
  111. //
  112. // implicit conversions
  113. //
  114. implicit def iPipeProcess(process: Process) =
  115. new PipeProcess(process)(this);
  116. implicit def iPipeInputStream(stream: InputStream) =
  117. new PipeInputStream(stream);
  118. implicit def iPipeInputStream(file: File) =
  119. new PipeInputStream(iInputStream(file));
  120. /**
  121. * Gets a FileInputStream for the given file. If the filename
  122. * ends with .gz, automatically wraps the returned stream with
  123. * a java.util.zip.GZIPInputStream.
  124. */
  125. implicit def iInputStream(file: File): InputStream = {
  126. val fis = new java.io.BufferedInputStream(new java.io.FileInputStream(file));
  127. if (file.getName.toLowerCase.endsWith(".gz")) {
  128. return new java.util.zip.GZIPInputStream(fis);
  129. } else {
  130. return fis;
  131. }
  132. }
  133. /**
  134. * Gets a FileOutputStream for the given file. If the filename
  135. * ends with .gz, automatically wraps the returned stream with
  136. * a java.util.zip.GZIPOutputStream.
  137. */
  138. implicit def iOutputStream(file: File): OutputStream = {
  139. val fos = new java.io.BufferedOutputStream(new java.io.FileOutputStream(file));
  140. if (file.getName.toLowerCase.endsWith(".gz")) {
  141. return new java.util.zip.GZIPOutputStream(fos);
  142. } else {
  143. return fos;
  144. }
  145. }
  146. /**
  147. * Returns a file with the given name, relative to the current
  148. * directory (if found and path does not start with
  149. */
  150. implicit def File(path: String): File = {
  151. if (!path.startsWith(java.io.File.separator)) {
  152. new File(_cwd, path);
  153. } else {
  154. new File(path);
  155. }
  156. }
  157. def File(base: java.io.File, path: String) = new File(base, path);
  158. implicit def iPipeIterator(lines: Iterator[String]) =
  159. new PipeIterator(lines)(this);
  160. implicit def iPipeIterator(lines: Iterable[String]) =
  161. new PipeIterator(lines.iterator)(this);
  162. private def error(message: String): Unit = {
  163. throw new PipesException(message);
  164. }
  165. }
  166. /**
  167. * To get started with a global pipes shell, use:
  168. *
  169. * import scalanlp.util.Pipes.global._
  170. *
  171. * And take a look at the example code in the Pipes object's main method.
  172. */
  173. object Pipes {
  174. type HasLines = {
  175. def getLines(): Iterator[String];
  176. }
  177. /**A global instance for easy imports */
  178. val global = new Pipes();
  179. def apply() = {
  180. new Pipes();
  181. }
  182. }
  183. object PipesExample {
  184. import Pipes.global._;
  185. def main(argv: Array[String]) {
  186. sh("echo '(no sleep) prints 1st'") | stdout;
  187. sh("sleep 1; echo '(sleep 1) prints 2nd'") | stdout;
  188. sh("echo '(stderr redirect) should show up on stdout' | cat >&2") |& stdout;
  189. sh("echo '(stderr redirect) should also show up on stdout' | cat >&2") |& sh("cat") | stdout;
  190. sh("echo '(pipe test line 1) should be printed'; echo '(pipe test line 2) should not be printed'") | sh("grep 1") | stdout;
  191. sh("echo '(translation test) should sound funny'") | sh("perl -pe 's/(a|e|i|o|u)+/oi/g';") | stdout;
  192. stdin | sh("egrep '[0-9]'") | stdout;
  193. (1 to 10).map(_.toString) | stderr;
  194. for (line <- sh("ls").getLines) {
  195. println(line.toUpperCase);
  196. }
  197. }
  198. }
  199. /**
  200. * Helper methods for PipeProcess
  201. *
  202. * @author dramage
  203. */
  204. object PipeIO {
  205. /**
  206. * Read all bytes from the given input stream to the given output
  207. * stream, closing the input stream when finished reading. Does
  208. * not close the output stream.
  209. */
  210. def drain(in: InputStream, out: OutputStream) {
  211. val buffer = new Array[Byte](1024);
  212. var numRead = 0;
  213. do {
  214. numRead = in.read(buffer, 0, buffer.length);
  215. if (numRead > 0) {
  216. // read some bytes
  217. out.write(buffer, 0, numRead);
  218. } else if (numRead == 0) {
  219. // read no bytes, but not yet EOF
  220. Thread.sleep(100l);
  221. }
  222. } while (numRead >= 0)
  223. in.close();
  224. }
  225. /**
  226. * Reads all lines in the given input stream using Java's
  227. * BufferedReader. The returned lines do not have a trailing
  228. * newline character.
  229. */
  230. def readLines(in: InputStream): Iterator[String] = {
  231. val reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));
  232. return new Iterator[String]() {
  233. var line = prepare();
  234. override def hasNext =
  235. line != null;
  236. override def next = {
  237. val rv = line;
  238. line = prepare();
  239. rv;
  240. }
  241. def prepare() = {
  242. val rv = reader.readLine();
  243. if (rv == null) {
  244. reader.close();
  245. }
  246. rv;
  247. }
  248. };
  249. }
  250. }
  251. /**
  252. * A richer Process object used for linking together in pipes.
  253. *
  254. * @author dramage
  255. */
  256. class PipeProcess(val process: Process)(implicit pipes: Pipes) {
  257. import PipeIO._
  258. /**where stdout and stderr go. */
  259. protected var out: OutputStream = pipes.stdout;
  260. protected var err: OutputStream = pipes.stderr;
  261. def waitFor: Int = process.waitFor();
  262. /**Close output pipes (on finish) if they are not stdout and stderr */
  263. private def closePipes() {
  264. if (out != pipes.stdout && out != pipes.stderr) {
  265. out.close();
  266. }
  267. if (err != pipes.stdout && err != pipes.stderr) {
  268. err.close();
  269. }
  270. }
  271. def |(next: PipeProcess): PipeProcess = {
  272. // stdout goes to the next process
  273. this.out = next.process.getOutputStream;
  274. spawn {
  275. val waitForStdin = future {drain(process.getInputStream, out); }
  276. val waitForStderr = future {drain(process.getErrorStream, err); }
  277. waitForStdin();
  278. closePipes();
  279. }
  280. return next;
  281. }
  282. def |&(next: PipeProcess): PipeProcess = {
  283. // stdout and stderr both go to the next process
  284. this.out = next.process.getOutputStream;
  285. this.err = next.process.getOutputStream;
  286. spawn {
  287. val waitForStdin = future {drain(process.getInputStream, out); }
  288. val waitForStderr = future {drain(process.getErrorStream, err); }
  289. waitForStdin();
  290. waitForStderr();
  291. closePipes();
  292. }
  293. return next;
  294. }
  295. /**Piping to a process happens immediately via spawning. */
  296. def |(process: Process): PipeProcess = {
  297. spawn {
  298. this | process.getOutputStream;
  299. }
  300. return new PipeProcess(process);
  301. }
  302. /**Piping to a process happens immediately via spawning. */
  303. def |&(process: Process): PipeProcess = {
  304. spawn {
  305. this |& process.getOutputStream;
  306. }
  307. return new PipeProcess(process);
  308. }
  309. /**Redirects the given input stream as the source for the process */
  310. def <(instream: InputStream): Process = {
  311. spawn {
  312. val out = process.getOutputStream;
  313. drain(instream, process.getOutputStream);
  314. out.close();
  315. }
  316. return process;
  317. }
  318. /**
  319. * Redirects output from the process to the given output stream.
  320. * Blocks until the process completes.
  321. */
  322. def |(outstream: OutputStream): Process = {
  323. this.out = outstream;
  324. val waitForStdin = future {drain(process.getInputStream, out); }
  325. val waitForStderr = future {drain(process.getErrorStream, err); }
  326. waitForStdin();
  327. closePipes();
  328. process;
  329. }
  330. /**
  331. * Redirects stdout and stderr from the process to the given output stream.
  332. * Blocks until the process completes.
  333. */
  334. def |&(outstream: OutputStream): Process = {
  335. this.out = outstream;
  336. this.err = outstream;
  337. val waitForStdin = future {drain(process.getInputStream, out); }
  338. val waitForStderr = future {drain(process.getErrorStream, err); }
  339. waitForStdin();
  340. waitForStderr();
  341. closePipes();
  342. process;
  343. }
  344. /**Pipes to a function that accepts an InputStream. */
  345. def |[T](func: (InputStream => T)): T =
  346. func(process.getInputStream);
  347. /**Reads the lines from this file. */
  348. def getLines: Iterator[String] =
  349. readLines(process.getInputStream);
  350. }
  351. /**
  352. * An alternative richer InputStream that can be piped to an OutputStream,
  353. * Process, or function.
  354. *
  355. * @author dramage
  356. */
  357. class PipeInputStream(var stream: InputStream) {
  358. import PipeIO._;
  359. /**
  360. * Pipe to an OutputStream. Returns when all bytes have been
  361. * written to out. Does not close out.
  362. */
  363. def |(out: OutputStream): Unit =
  364. drain(stream, out);
  365. /**
  366. * Pipe to Process, returning that Process instance. Returns
  367. * immediately. Spawns a background job to write all bytes
  368. * from the incoming stream to the process.
  369. */
  370. def |(process: PipeProcess): Process =
  371. process < stream;
  372. /**Pipes to a function that accepts an InputStream. */
  373. def |[T](func: (InputStream => T)): T =
  374. func(stream);
  375. /**Returns all lines in this Stream. */
  376. def getLines: Iterator[String] =
  377. readLines(stream);
  378. }
  379. /**
  380. * A pipeable iterator of Strings, to be written as lines to a stream.
  381. */
  382. class PipeIterator(lines: Iterator[String])(implicit pipes: Pipes) {
  383. /**
  384. * Writes all lines to the given process. Returns immediately.
  385. */
  386. def |(process: PipeProcess): Process = {
  387. val pipeIn = new java.io.PipedInputStream();
  388. val pipeOut = new java.io.PipedOutputStream(pipeIn);
  389. spawn {this | pipeOut; }
  390. process < pipeIn;
  391. }
  392. /**
  393. * Writes all lines to the given OutputStream, closing it when done
  394. * if it is not System.out or System.err.
  395. */
  396. def |(outstream: OutputStream) = {
  397. val ps = new java.io.PrintStream(outstream);
  398. for (line <- lines) {
  399. ps.println(line);
  400. }
  401. if (!(outstream == pipes.stdout || outstream == pipes.stderr)) {
  402. ps.close;
  403. }
  404. }
  405. }
  406. /**
  407. * Runtime exception thrown by the Pipes framework.
  408. *
  409. * @author dramage
  410. */
  411. class PipesException(message: String) extends RuntimeException(message);