copie 0.2.0

Copies file from or to paths specified in environment variables and command-line.
//     copie - A file copy tool which copies files from and to paths specified in environment variables.
//
//         The MIT License (MIT)
//
//      Copyright (c) Jonathan H. R. Lopes (https://github.com/JonathanxD) <jhrldev@gmail.com>
//      Copyright (c) contributors
//
//      Permission is hereby granted, free of charge, to any person obtaining a copy
//      of this software and associated documentation files (the "Software"), to deal
//      in the Software without restriction, including without limitation the rights
//      to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//      copies of the Software, and to permit persons to whom the Software is
//      furnished to do so, subject to the following conditions:
//
//      The above copyright notice and this permission notice shall be included in
//      all copies or substantial portions of the Software.
//
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//      THE SOFTWARE.

//! *copie* is a command-line tool to copy files from or to paths specified in environment variables
//! and in command-line arguments.
//!
//! ## Usage
//!
//! ### Copy from a file into the file specified in command-line
//!
//! ```fish
//! COPIE_FROM=myfile.json copie target_file.json
//! ```
//!
//! This command copies the file `myfile.json` to `target_file.json`, overwriting the `target_file.json`
//! if it already exists.
//!
//! ### Copy from file specified in command-line to another file.
//!
//! ```fish
//! COPIE_TO=target_file.json copie myfile.json
//! ```
//!
//! This command copies the contents of `myfile.json` into the `target_file.json`, overwriting the target
//! if it already exists.
//!
//! ### Copy from a file specified in environment variable to another file specified in environment variable.
//!
//! ```fish
//! COPIE_TO=target_file.json COPIE_FROM=myfile.json copie
//! ```
//!
//! This command copies the contents of `myfile.json` into the `target_file.json`, overwriting the target
//! if it already exists.
//!
//! ### Using in place of an editor to capture the file contents.
//!
//! Some applications use the editor specified in `$VISUAL` or `$EDITOR` environment variable,
//! such as the awesome [edit](https://crates.io/crates/edit) crate does, however, while doing integration
//! with other applications, it may be interesting to open these files in the editor UI instead of the editor
//! provided in these variables, and some applications are not able to handle opening these files
//! natively while being able to "block" the command while the file is open.
//!
//! **Copie** provides a easy way to capture the file contents and copy to another file, however, keep
//! in mind that **Copie** exits immediately after copying, with the exit code 0 by default, meaning that
//! if the application who request the editor uses the exit code as a signal to check if editing succeeded,
//! then it may conclude the operation, which could not be a desired result. To overcome this, **copie**
//! exit code could be changed by setting the `$COPIE_EXIT_CODE` environment variable, by doing that,
//! **copie** will always exit with the specified code in case of success. For failures, **copie** always exits
//! with the `-1` code and prints the error message to the standard error output.
//!
//! Example of use of **copie** as an editor capture tool:
//!
//! ```fish
//! VISUAL="copie" EDITOR="copie" COPIE_TO=./tmp/myfile pijul record
//! ```
//!
//! ## Practical uses
//!
//! *copie* is used by [Dracon for IntelliJ](https://nest.pijul.com/Jonathan/Dracon) to act like an
//! editor and capture the file contents and load in [IntelliJ IDEA](https://www.jetbrains.com/pt-br/idea/)
//! editor to provide an immersive integration.
//!
//!
//!

extern crate clap;
use clap::{Arg, App};
use std::env;
use std::fs;
use std::path::PathBuf;

fn main() {
    let exit_code = env::var("COPIE_EXIT_CODE").map_err(|e| format!("{:?}", e));
    let exit_code_as_int = if let Ok(code) = exit_code {
        code.parse::<i32>().map_err(|e| format!("{:?}", e))
    } else {
        Err(exit_code.err().unwrap())
    };

    std::process::exit(match run_app() {
        Ok(_) => exit_code_as_int.unwrap_or(0),
        Err(err) => {
            eprintln!("error: {:?}", err);
            -1
        }
    });
}

fn run_app() -> Result<(), String> {
    let matches = App::new("copie")
        .version("0.2.0")
        .author("Jonathan H. R. Lopes <jhrldev@gmail.com>")
        .about("Copies data from or to specified file.\nPaths must be specified as environment variables: COPIE_FROM specifies the file to copy from and COPIE_TO specifies the file to copy to.\nWhen COPIE_TO is combined with an input file specified as first argument, the content of the file specified in argument is copied to the file specified in COPIE_TO environment variable.\nWhen COPIE_FROM is combined with an input file specified as first argument, the content of the file specified in COPIE_FROM is copied to the file specified as first argument.\nWhen COPIE_FROM is combined with COPIE_TO, the file specified in the first one is copied to the file specified in the second one.")
        .arg(Arg::new("FILE")
            .about("Sets the file to read from or to write to.")
            .required(false)
            .index(1))
        .get_matches();

    let file_to_read_or_replace =  matches.value_of("FILE").map(|i| i.to_string()).map(|i| PathBuf::from(i));
    let from_var = env::var("COPIE_FROM").map(|i| PathBuf::from(i));
    let to_var = env::var("COPIE_TO").map(|i| PathBuf::from(i));
    let from_str_var = env::var("COPIE_FROM_STRING");

    if from_var.is_ok() && to_var.is_ok() && file_to_read_or_replace.is_some() {
        return Err(format!("COPIE_FROM, COPIE_TO and a file parameter was specified, copie could not copy because it is impossible to determine which file is to copy from or to."))
    }

    if from_var.is_ok() && from_str_var.is_ok() {
        return Err(format!("COPIE_FROM_STRING and COPIE_FROM was specified, one only source should be specified."))
    }

    if from_str_var.is_ok() && to_var.is_ok() && file_to_read_or_replace.is_some() {
        return Err(format!("COPIE_FROM_STRING, COPIE_TO and a file parameter was specified, copie could not determine the file or resource to copy from."))
    }

    let from_path = if let Ok(from) = from_var {
        if !from.exists() {
            return Err(format!("Path '{}' specified in variable COPIE_FROM does not exists!", from.to_string_lossy()))
        }

        if from.is_dir() {
            return Err(format!("Path '{}' specified in variable COPIE_FROM is a directory, COPEE does not copy directories!", from.to_string_lossy()))
        }

        Some(from)
    } else {
        None
    };

    let from_str = if let Ok(from) = from_str_var {
        Some(from)
    } else {
        None
    };

    let to_path = if let Ok(to) = to_var {

        if to.is_dir() {
            return Err(format!("Path '{}' specified in variable COPIE_TO is a directory, COPEE does not copy directories!", to.to_string_lossy()))
        }

        Some(to)
    } else {
        None
    };

    if from_path.is_some() && to_path.is_some() {
        let from = from_path.unwrap();
        let to = to_path.unwrap();

        if from.is_dir() {
            return Err(format!("Path '{}' specified in COPIE_FROM variable is a directory, COPEE does not copy directories.", from.to_string_lossy()))
        }

        if to.exists() {
            return Err(format!("Path '{}' specified in COPIE_TO already exists, COPEE does not replace files.", to.to_string_lossy()))
        }

        let result = fs::copy(from.clone(), to.clone());

        if let Err(err) = result {
            return Err(format!("Failed to copy from '{}' to '{}': {:?}", from.to_string_lossy(), to.to_string_lossy(), err))
        }

        return Ok(());
    }

    if from_str.is_some() && to_path.is_some() {
        let from = from_str.unwrap();
        let to = to_path.unwrap();

        if to.exists() {
            return Err(format!("Path '{}' specified in COPIE_TO already exists, COPEE does not replace files.", to.to_string_lossy()))
        }

        let result = fs::write(to.clone(), from.clone());

        if let Err(err) = result {
            return Err(format!("Failed write string '{}' to '{}': {:?}", from, to.to_string_lossy(), err))
        }

        return Ok(());
    }

    let file_to_read_or_replace_path =
        if let Some(file) = file_to_read_or_replace {

            if !file.exists() {
                return Err(format!("Path '{}' specified in the command line does not exists!", file.to_string_lossy()))
            }

            if file.is_dir() {
                return Err(format!("Path '{}' specified in the command line is a directory, COPEE does not copy directories!", file.to_string_lossy()))
            }

            Some(file)
        } else {
            None
        };

    if from_path.is_some() && file_to_read_or_replace_path.is_some() {
        let from = from_path.unwrap();
        let target = file_to_read_or_replace_path.unwrap();

        let result = fs::copy(from.clone(), target.clone());

        if let Err(err) = result {
            return Err(format!("Failed to copy from '{}' to '{}': {:?}", from.to_string_lossy(), target.to_string_lossy(), err))
        }

        return Ok(());
    }

    if from_str.is_some() && file_to_read_or_replace_path.is_some() {
        let from = from_str.unwrap();
        let target = file_to_read_or_replace_path.unwrap();

        let result = fs::write(target.clone(), from.clone());

        if let Err(err) = result {
            return Err(format!("Failed to write string contents '{}' to '{}': {:?}", from, target.to_string_lossy(), err))
        }

        return Ok(());
    }

    if to_path.is_some() && file_to_read_or_replace_path.is_some() {
        let to = to_path.unwrap();
        let origin = file_to_read_or_replace_path.unwrap();

        let result = fs::copy(origin.clone(), to.clone());

        if let Err(err) = result {
            return Err(format!("Failed to copy from '{}' to '{}': {:?}", origin.to_string_lossy(), to.to_string_lossy(), err))
        }

        return Ok(());
    }

    if file_to_read_or_replace_path.is_none() {
        return Err(format!("Missing file to read or to replace."))
    }

    if from_path.is_none() || to_path.is_none() {
        return Err(format!("Missing environment variable COPIE_FROM or COPIE_TO!"))
    }

    return Err(format!("Unknown error!"))
}