Small shell task launcher review

Hi,

I'm fairly new to Rust, and give myself the task to develop a small Shell task launcher for Linux.
The goal is:

  • Ability to register a task.
  • Ability to find and run a registered task.
  • Provide output and status of execution of the given task.

So i write this code : (Playground)

use std::error::Error;
use std::process;
use std::collections::HashMap;

struct TaskStatus {
    exit_code: i32,
    stdout: String,
    stderr: String,
}

impl TaskStatus {
    fn new(exit_code: i32, stdout: String, stderr: String) -> TaskStatus {
        TaskStatus {
            exit_code,
            stdout,
            stderr,
        }
    }
}

struct Task {
    title: String,
    command: String,
}

impl Task {
    fn new(title: String, command: String) -> Task {
        Task {
            title,
            command,
        }
    }

    fn show(&self) -> &Task {
        println!("{}", &self.title);
        self
    }

    fn run(&self) -> Result<TaskStatus, Box<dyn Error>> {
        let output = process::Command::new("sh")
            .arg("-c")
            .arg(&self.command)
            .output();
        let output = match output {
            Err(error) => {
                println!("{}", error);
                return Err(Box::new(error));
            },
            Ok(out) => out,
        };
        
        let exit_code = output.status.code();
        let exit_code = match exit_code {
            None => {
                return Err("Unable to get an exit code.".into());
            },
            Some(code) => code,
        };
        Ok(TaskStatus::new(
            exit_code,
            String::from_utf8_lossy(&output.stdout).to_string(),
            String::from_utf8_lossy(&output.stderr).to_string(),
        ))
    }
}

struct Tasks {
    list: HashMap<String, Task>,
}

impl<'a> Tasks {
    fn new() -> Self {
        Tasks {
            list: HashMap::new(),
        }
    }

    fn add(&'a mut self, name: String, title: String, command: String) {
        let _ = &self.list.insert(
            name,
            Task::new(
                title,
                command
            )
        );
    }

}

fn main() {
    let mut tasks = Tasks::new();
    tasks.add(
        "deploy".to_string(),
        "Launching deploy: ".to_string(),
        "echo Deploying myproject".to_string(),
    );

    tasks.add(
        "toto".to_string(),
        "Launching toto: ".to_string(),
        "ls -l /toto".to_string(),
    );

    let task = tasks.list.get("toto");
    let task = match task {
        Some(ftask) => ftask,
        None => {
            eprintln!("No task defined by this name: toto.");
            process::exit(1)
        }
    };

    let rs = task.show().run().unwrap();
    println!("Exit Code: {}", rs.exit_code);
    println!("Stdout: {}", rs.stdout);
    println!("Stderr: {}", rs.stderr);
}

This code works properly, and cargo clippy says it is all good. But i really would like some experienced rustaceans to review it, and give me some advices on how to improve this code, either logically, or syntactically.

Best regards, and happy coding !

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.