5
\$\begingroup\$

This is my first hands-on experience with Rust. The program actually works and I really like how it functions. I'm just very interested in what code improvements I could do.

I wrote an application in Rust to help me manage my php environments. The binary can be run like:

timpack-cli composer install # run composer install
timpack-cli php72 composer install # run composer install with 'php72'

In the future I want to add in detetection to check what php version should be used.

use std::collections::HashMap;
use std::env;
use std::process::{exit, Command, Output, Stdio};
use std::str;

fn main() {
    let input_arguments: Vec<String> = env::args().collect();

    let interpreters = ["php", "php70", "php71", "php72"];
    let mut interpreter = String::new();
    let mut binary = String::new();

    // Create a hashmap for the command aliases.
    let mut aliases = HashMap::new();
    aliases.insert(string("m1"), string("n98-magerun"));
    aliases.insert(string("m2"), string("n98-magerun2"));
    aliases.insert(string("cc"), string("composer"));

    // Start from index one, since first element is the executed binary, which we don't need right now.
    for i in 1..input_arguments.len() {
        let arg: &str = &input_arguments[i];

        if i == 1 {
            if interpreters.contains(&arg) {
                interpreter = arg.to_string();
            } else  {
                binary = arg.to_string();
            }
        }

        if i == 2 && !interpreter.is_empty() {
            binary = arg.to_string();
            break;
        }
    }

    // Fail fast, no binary to run.
    if binary.is_empty() {
        println!("No binary given.");
        exit(1);
    }

    // Resolve alias usage if necessary.
    if aliases.contains_key(&binary) {
        binary = aliases.get(&binary).unwrap().to_string();
    }

    let mut executable = binary;
    let mut command_arguments_index = 2;
    let mut command_arguments = Vec::new();

    // Prepare command execution if binary needs to be run with interpreter.
    if !interpreter.is_empty() {
        let output = Command::new("which")
                .arg(executable)
                .output()
                .expect("Execution of 'which' failed.");

        command_arguments.push(output_to_string(output));

        executable = interpreter;
        command_arguments_index += 1;
    }

    // Push all remaining stdin args to the new command arguments
    for i in command_arguments_index..input_arguments.len() {
        &command_arguments.push(input_arguments[i].to_owned());
    }

    // Run command and wait for status.
    let status = Command::new(executable)
              .args(command_arguments)
              .stdin(Stdio::inherit())
              .stdout(Stdio::inherit())
              .stderr(Stdio::inherit())
              .status()
              .expect("Failed to execute command");

    // Exit program with status code of command execution.
    exit(status.code().unwrap());
}

/**
 * Create String from str.
 */
fn string(value: &str) -> String {
    return value.to_owned();
}

/**
 * Get string from Output type.
 *
 * @TODO: What about stderr?
 */
fn output_to_string(output: Output) -> String {
    return String::from_utf8(output.stdout).unwrap().trim().to_string();
}

Also, it's available on Github, if that's easier.

\$\endgroup\$
2
\$\begingroup\$

A couple of things pop out at me.

  1. Prefer `Iterators‘ over “raw” indexing for loops.

    Instead of this

    for i in 1..input_arguments.len()
    

    You can just loop over the arguments.

    for argument in input_arguments
    
  2. There’s already a to_string() method on &str. No need to define it yourself.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.