/src/main.rs

https://github.com/zserge/tinysh · Rust · 114 lines · 110 code · 4 blank · 0 comment · 14 complexity · 5e4dd3512ccb3382bdf5b796a8aae65b MD5 · raw file

  1. use std::fs::File;
  2. use std::io::Write;
  3. use std::io::{Error, ErrorKind};
  4. use std::process::{Command, Stdio};
  5. use std::{env, io};
  6. #[derive(Debug, Clone)]
  7. enum Token {
  8. Blank,
  9. Redir(i32),
  10. Delim(bool),
  11. Normal(char),
  12. }
  13. fn token(x: Option<&char>) -> Token {
  14. match x {
  15. Some(&c) => match c {
  16. '|' => Token::Delim(true),
  17. '<' => Token::Redir(0),
  18. '>' => Token::Redir(1),
  19. ' ' | '\t' | '\n' | '\r' => Token::Blank,
  20. _ => Token::Normal(c),
  21. },
  22. None => Token::Delim(false),
  23. }
  24. }
  25. fn run<I>(mut it: std::iter::Peekable<I>, output: Stdio) -> Result<(), Error>
  26. where
  27. I: Iterator<Item = char>,
  28. {
  29. let mut is_pipe = false;
  30. let mut args = Vec::<String>::new();
  31. let mut io_in = Stdio::inherit();
  32. let mut io_out = output;
  33. while let Some(tok) = it.next() {
  34. match token(Some(&tok)) {
  35. Token::Delim(p) => {
  36. is_pipe = p;
  37. break;
  38. }
  39. Token::Normal(c) => {
  40. let mut word = String::new();
  41. word.push(c);
  42. while let Token::Normal(c) = token(it.peek()) {
  43. it.next();
  44. word.push(c);
  45. }
  46. args.push(word.chars().rev().collect::<String>());
  47. }
  48. Token::Redir(fd) => match args.pop() {
  49. Some(path) => match fd {
  50. 0 => io_in = Stdio::from(File::open(path)?),
  51. 1 => io_out = Stdio::from(File::create(path)?),
  52. _ => return Err(Error::new(ErrorKind::Other, "bad redirection fd")),
  53. },
  54. None => {
  55. return Err(Error::new(
  56. ErrorKind::Other,
  57. "redirection filename expected",
  58. ));
  59. }
  60. },
  61. Token::Blank => {}
  62. }
  63. }
  64. if args.is_empty() {
  65. return Ok(());
  66. }
  67. args.reverse();
  68. if args[0] == "cd" {
  69. let path = args
  70. .into_iter()
  71. .nth(1)
  72. .unwrap_or(env::var("HOME").unwrap_or("/".to_string()));
  73. env::set_current_dir(path)?;
  74. return Ok(());
  75. }
  76. if is_pipe {
  77. io_in = Stdio::piped()
  78. }
  79. let mut args_iter = args.iter();
  80. let pathname = args_iter.next().unwrap();
  81. let mut child = Command::new(pathname)
  82. .args(args_iter)
  83. .stdin(io_in)
  84. .stdout(io_out)
  85. .spawn()?;
  86. if is_pipe {
  87. run(it, Stdio::from(child.stdin.take().unwrap()))?;
  88. }
  89. child.wait()?;
  90. Ok(())
  91. }
  92. fn main() {
  93. loop {
  94. print!("$ ");
  95. let _ = io::stdout().flush(); // continue even if flush fails
  96. let mut input = String::new();
  97. match io::stdin().read_line(&mut input) {
  98. Ok(0) => return,
  99. Ok(_) => {
  100. if let Err(e) = run(input.chars().rev().peekable(), Stdio::inherit()) {
  101. println!("error: {}", e);
  102. }
  103. }
  104. Err(e) => {
  105. println!("io error: {}", e);
  106. return;
  107. }
  108. }
  109. }
  110. }