<commit_before>use pyembed::{ExtensionModule, MainPythonInterpreter};
use std::path::PathBuf;
use structopt::{clap::AppSettings::*, StructOpt};

include!(env!("PYOXIDIZER_DEFAULT_PYTHON_CONFIG_RS"));

#[derive(Debug, StructOpt)]
enum IOSCommands {
    Init,
    Ids,
}

#[derive(Debug, StructOpt)]
enum BuildCommands {
    #[structopt(about = "Demonstration SVG output for debugging")]
    Svg {
        #[structopt(flatten)]
        in_out: InOutPaths,
    },

    #[structopt(about = "Generates an .apk for Android")]
    Android {
        #[structopt(long = "kbd-repo", default_value = "divvun/giellakbd-android")]
        kbd_repo: String,

        #[structopt(long = "kbd-branch", default_value = "develop")]
        kbd_branch: String,

        #[structopt(long = "divvunspell-repo", default_value = "divvun/divvunspell")]
        divvunspell_repo: String,

        #[structopt(long = "divvunspell-branch", default_value = "develop")]
        divvunspell_branch: String,

        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[cfg(target_os = "macos")]
    #[structopt(about = "Generates an .ipa for iOS")]
    IOS {
        #[structopt(subcommand)]
        command: Option<IOSCommands>,

        #[structopt(long = "kbd-repo", default_value = "divvun/giellakbd-ios")]
        kbd_repo: String,

        #[structopt(long = "kbd-branch", default_value = "develop")]
        kbd_branch: String,

        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(about = "Generates installers for Windows 7/8 and 8.1+")]
    Win {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[cfg(target_os = "macos")]
    #[structopt(about = "Generates installers for macOS")]
    Mac {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    X11 {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(flatten)]
        build_mode: BuildMode,

        #[structopt(long = "standalone")]
        standalone: bool,
    },

    #[structopt(about = "Generates m17n output")]
    M17n {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(about = "Generates Chrome OS bundles for putting on the Chrome App Store")]
    Chrome {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(setting(Hidden))]
    Qr {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short, long = "layout")]
        layout: String,
    },

    #[structopt(setting(Hidden))]
    ErrorModel {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short, long = "layout")]
        layout: String,
    },
}

#[derive(Debug, StructOpt)]
struct InOutPaths {
    #[structopt(short, long = "output", default_value = ".", parse(from_os_str))]
    output_path: PathBuf,

    #[structopt(parse(from_os_str))]
    project_path: PathBuf,
}

#[derive(Debug, StructOpt)]
struct BuildMode {
    /// Compile in 'release' mode (where necessary).
    #[structopt(short = "R", long = "release")]
    release: bool,

    /// Continuous integration build
    #[structopt(long = "ci")]
    ci: bool,
}

#[derive(Debug, StructOpt)]
enum NewCommands {
    Layout {
        #[structopt(short, long = "output", default_value = ".", parse(from_os_str))]
        output_path: PathBuf,

        #[structopt(parse(from_os_str))]
        project_path: PathBuf,
    },
    Bundle {
        #[structopt(short, long = "output", default_value = ".", parse(from_os_str))]
        output_path: PathBuf,

        bundle_name: String,
    },
}

#[derive(Debug, StructOpt)]
enum Commands {
    #[structopt(
        about = "Generate output for a given .kbdgen bundle",
        setting(DisableHelpSubcommand)
    )]
    Build {
        #[structopt(long = "github-username")]
        github_username: Option<String>,

        #[structopt(long = "github-token")]
        github_token: Option<String>,

        #[structopt(subcommand)]
        command: BuildCommands,
    },
    #[structopt(
        about = "Generate new bundles or layout templates",
        setting(DisableHelpSubcommand)
    )]
    New {
        #[structopt(subcommand)]
        command: NewCommands,
    },
    #[structopt(setting(Hidden))]
    Repl,
}

#[derive(Debug, StructOpt)]
#[structopt(
    name = "kbdgen",
    about = "The best solution to generating keyboards.",
    setting(DisableHelpSubcommand)
)]
struct Opts {
    #[structopt(long = "logging", default_value = "info")]
    logging: String,

    #[structopt(subcommand)]
    command: Commands,
}

impl BuildCommands {
    fn to_py_args<'a>(
        &'a self,
        github_username: Option<&'a str>,
        github_token: Option<&'a str>,
        logging: &'a str,
    ) -> Result<Vec<&'a str>, Box<dyn std::error::Error>> {
        use BuildCommands::*;

        let mut args = match self {
            Svg {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
            } => vec![
                "-t",
                "svg",
                "-o",
                &*output_path.to_str().unwrap(),
                &*project_path.to_str().unwrap(),
            ],
            Android {
                kbd_repo,
                kbd_branch,
                divvunspell_repo,
                divvunspell_branch,
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec![
                    "-t",
                    "android",
                    "--kbd-repo",
                    kbd_repo,
                    "--kbd-branch",
                    kbd_branch,
                    "--divvunspell-repo",
                    divvunspell_repo,
                    "--divvunspell-branch",
                    divvunspell_branch,
                    "-o",
                    &*output_path.to_str().unwrap(),
                ];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());

                args
            }
            #[cfg(target_os = "macos")]
            IOS {
                command,
                kbd_repo,
                kbd_branch,
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec![
                    "-t",
                    "ios",
                    "--kbd-repo",
                    kbd_repo,
                    "--kbd-branch",
                    kbd_branch,
                    "-o",
                    &*output_path.to_str().unwrap(),
                ];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                match command {
                    Some(IOSCommands::Init) => {
                        args.push("--command");
                        args.push("init");
                    }
                    Some(IOSCommands::Ids) => {
                        args.push("--command");
                        args.push("ids");
                    }
                    _ => {}
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            Chrome {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec!["-t", "chrome", "-o", &*output_path.to_str().unwrap()];

                if *release {
                    args.push("-R");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            Win {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec!["-t", "win", "-o", &*output_path.to_str().unwrap()];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            #[cfg(target_os = "macos")]
            Mac {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec!["-t", "mac", "-o", &*output_path.to_str().unwrap()];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            M17n { .. } => return Err(From::from("M17n isn't supported in Python".to_string())),
            Qr {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                layout,
            } => vec![
                "-t",
                "qr",
                "-o",
                &*output_path.to_str().unwrap(),
                "--command",
                layout,
                &*project_path.to_str().unwrap(),
            ],
            ErrorModel {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                layout,
            } => vec![
                "-t",
                "errormodel",
                "-o",
                &*output_path.to_str().unwrap(),
                "--command",
                layout,
                &*project_path.to_str().unwrap(),
            ],
            X11 { .. } => unreachable!("covered in previous match"),
        };

        if let Some(gh_username) = github_username {
            args.push("--github-username");
            args.push(&*gh_username);
        }

        if let Some(gh_token) = github_token {
            args.push("--github-token");
            args.push(&*gh_token);
        }

        args.push("--logging");
        args.push(logging);

        Ok(args)
    }
}

fn python_config() -> pyembed::PythonConfig {
    let mut config = default_python_config();

    let mod_language_tags = ExtensionModule {
        name: std::ffi::CString::new("language_tags").unwrap(),
        init_func: py_language_tags::PyInit_language_tags,
    };

    let mod_logger = ExtensionModule {
        name: std::ffi::CString::new("rust_logger").unwrap(),
        init_func: py_logger::PyInit_rust_logger,
    };

    let mod_reqwest = ExtensionModule {
        name: std::ffi::CString::new("reqwest").unwrap(),
        init_func: py_reqwest::PyInit_reqwest,
    };

    config.extra_extension_modules = vec![mod_language_tags, mod_logger, mod_reqwest];
    config
}

fn launch_py_kbdgen(args: &[&str]) -> i32 {
    // Load the default Python configuration as derived by the PyOxidizer config
    // file used at build time.
    let config = python_config();

    // Construct a new Python interpreter using that config, handling any errors
    // from construction.
    match MainPythonInterpreter::new(config) {
        Ok(mut interp) => {
            // And run it using the default run configuration as specified by the
            // configuration. If an uncaught Python exception is raised, handle it.
            // This includes the special SystemExit, which is a request to terminate the
            // process.
            let args = format!(
                "[{}]",
                args.iter()
                    .map(|x| format!("{:?}", x))
                    .collect::<Vec<_>>()
                    .join(", ")
            );
            match interp.run_code(&format!("import kbdgen.cli; kbdgen.cli.run_cli({})", args)) {
                Ok(_) => 0,
                Err(msg) => {
                    let py = interp.acquire_gil();
                    msg.print(py);
                    1
                }
            }
        }
        Err(msg) => {
            eprintln!("{}", msg);
            1
        }
    }
}

fn launch_repl() -> i32 {
    let config = python_config();
    match MainPythonInterpreter::new(config) {
        Ok(mut interp) => {
            match interp.run_repl() {
                Ok(_) => 0,
                Err(msg) => {
                    let py = interp.acquire_gil();
                    msg.print(py);
                    1
                }
            }
        }
        Err(msg) => {
            eprintln!("{}", msg);
            1
        }
    }
}

fn main() {
    env_logger::Builder::from_default_env()
        .filter(Some("kbdgen"), log::LevelFilter::Info)
        .target(env_logger::Target::Stderr)
        .init();

    let opt = Opts::from_args();

    let logging = match &*opt.logging {
        "trace" => log::Level::Trace,
        "debug" => log::Level::Debug,
        "info" => log::Level::Info,
        "warn" => log::Level::Warn,
        "error" => log::Level::Error,
        x => {
            eprintln!("Invalid logging level: {}", x);
            std::process::exit(1);
        }
    };

    std::env::set_var("RUST_LOG", logging.to_string());

    match opt.command {
        Commands::Build {
            github_username,
            github_token,
            command,
        } => match command {
            BuildCommands::X11 {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                build_mode: BuildMode { .. },
                standalone,
            } => kbdgen::cli::to_xkb::kbdgen_to_xkb(
                &project_path,
                &output_path,
                &kbdgen::cli::to_xkb::Options { standalone },
            )
            .unwrap(),
            command => match command.to_py_args(
                github_username.as_ref().map(|x| &**x),
                github_token.as_ref().map(|x| &**x),
                &opt.logging,
            ) {
                Ok(args) => std::process::exit(launch_py_kbdgen(&args)),
                Err(e) => {
                    eprintln!("{:?}", e);
                    std::process::exit(1);
                }
            },
        },

        Commands::New { command } => match command {
            NewCommands::Bundle {
                bundle_name,
                output_path,
            } => match kbdgen::cli::from_cldr::cldr_to_kbdgen(&*output_path, &*bundle_name) {
                Ok(_) => std::process::exit(0),
                Err(e) => {
                    eprintln!("{:?}", e);
                    std::process::exit(1);
                }
            },
            NewCommands::Layout { .. } => {
                eprintln!("Not yet supported.");
                std::process::exit(1)
            }
        },

        Commands::Repl => {
            std::process::exit(launch_repl())
        }
    }
}
<commit_msg>Hook up M17N<commit_after>use pyembed::{ExtensionModule, MainPythonInterpreter};
use std::path::PathBuf;
use structopt::{clap::AppSettings::*, StructOpt};

include!(env!("PYOXIDIZER_DEFAULT_PYTHON_CONFIG_RS"));

#[derive(Debug, StructOpt)]
enum IOSCommands {
    Init,
    Ids,
}

#[derive(Debug, StructOpt)]
enum BuildCommands {
    #[structopt(about = "Demonstration SVG output for debugging")]
    Svg {
        #[structopt(flatten)]
        in_out: InOutPaths,
    },

    #[structopt(about = "Generates an .apk for Android")]
    Android {
        #[structopt(long = "kbd-repo", default_value = "divvun/giellakbd-android")]
        kbd_repo: String,

        #[structopt(long = "kbd-branch", default_value = "develop")]
        kbd_branch: String,

        #[structopt(long = "divvunspell-repo", default_value = "divvun/divvunspell")]
        divvunspell_repo: String,

        #[structopt(long = "divvunspell-branch", default_value = "develop")]
        divvunspell_branch: String,

        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[cfg(target_os = "macos")]
    #[structopt(about = "Generates an .ipa for iOS")]
    IOS {
        #[structopt(subcommand)]
        command: Option<IOSCommands>,

        #[structopt(long = "kbd-repo", default_value = "divvun/giellakbd-ios")]
        kbd_repo: String,

        #[structopt(long = "kbd-branch", default_value = "develop")]
        kbd_branch: String,

        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(about = "Generates installers for Windows 7/8 and 8.1+")]
    Win {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[cfg(target_os = "macos")]
    #[structopt(about = "Generates installers for macOS")]
    Mac {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short = "D", long = "dry-run")]
        dry_run: bool,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(about = "Generates X11 (XKB) output")]
    X11 {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(flatten)]
        build_mode: BuildMode,

        #[structopt(long = "standalone")]
        standalone: bool,
    },

    #[structopt(about = "Generates m17n output")]
    M17n {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(about = "Generates Chrome OS bundles for putting on the Chrome App Store")]
    Chrome {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(flatten)]
        build_mode: BuildMode,
    },

    #[structopt(setting(Hidden))]
    Qr {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short, long = "layout")]
        layout: String,
    },

    #[structopt(setting(Hidden))]
    ErrorModel {
        #[structopt(flatten)]
        in_out: InOutPaths,

        #[structopt(short, long = "layout")]
        layout: String,
    },
}

#[derive(Debug, StructOpt)]
struct InOutPaths {
    #[structopt(short, long = "output", default_value = ".", parse(from_os_str))]
    output_path: PathBuf,

    #[structopt(parse(from_os_str))]
    project_path: PathBuf,
}

#[derive(Debug, StructOpt)]
struct BuildMode {
    /// Compile in 'release' mode (where necessary).
    #[structopt(short = "R", long = "release")]
    release: bool,

    /// Continuous integration build
    #[structopt(long = "ci")]
    ci: bool,
}

#[derive(Debug, StructOpt)]
enum NewCommands {
    Layout {
        #[structopt(short, long = "output", default_value = ".", parse(from_os_str))]
        output_path: PathBuf,

        #[structopt(parse(from_os_str))]
        project_path: PathBuf,
    },
    Bundle {
        #[structopt(short, long = "output", default_value = ".", parse(from_os_str))]
        output_path: PathBuf,

        bundle_name: String,
    },
}

#[derive(Debug, StructOpt)]
enum Commands {
    #[structopt(
        about = "Generate output for a given .kbdgen bundle",
        setting(DisableHelpSubcommand)
    )]
    Build {
        #[structopt(long = "github-username")]
        github_username: Option<String>,

        #[structopt(long = "github-token")]
        github_token: Option<String>,

        #[structopt(subcommand)]
        command: BuildCommands,
    },
    #[structopt(
        about = "Generate new bundles or layout templates",
        setting(DisableHelpSubcommand)
    )]
    New {
        #[structopt(subcommand)]
        command: NewCommands,
    },
    #[structopt(setting(Hidden))]
    Repl,
}

#[derive(Debug, StructOpt)]
#[structopt(
    name = "kbdgen",
    about = "The best solution to generating keyboards.",
    setting(DisableHelpSubcommand)
)]
struct Opts {
    #[structopt(long = "logging", default_value = "info")]
    logging: String,

    #[structopt(subcommand)]
    command: Commands,
}

impl BuildCommands {
    fn to_py_args<'a>(
        &'a self,
        github_username: Option<&'a str>,
        github_token: Option<&'a str>,
        logging: &'a str,
    ) -> Result<Vec<&'a str>, Box<dyn std::error::Error>> {
        use BuildCommands::*;

        let mut args = match self {
            Svg {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
            } => vec![
                "-t",
                "svg",
                "-o",
                &*output_path.to_str().unwrap(),
                &*project_path.to_str().unwrap(),
            ],
            Android {
                kbd_repo,
                kbd_branch,
                divvunspell_repo,
                divvunspell_branch,
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec![
                    "-t",
                    "android",
                    "--kbd-repo",
                    kbd_repo,
                    "--kbd-branch",
                    kbd_branch,
                    "--divvunspell-repo",
                    divvunspell_repo,
                    "--divvunspell-branch",
                    divvunspell_branch,
                    "-o",
                    &*output_path.to_str().unwrap(),
                ];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());

                args
            }
            #[cfg(target_os = "macos")]
            IOS {
                command,
                kbd_repo,
                kbd_branch,
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec![
                    "-t",
                    "ios",
                    "--kbd-repo",
                    kbd_repo,
                    "--kbd-branch",
                    kbd_branch,
                    "-o",
                    &*output_path.to_str().unwrap(),
                ];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                match command {
                    Some(IOSCommands::Init) => {
                        args.push("--command");
                        args.push("init");
                    }
                    Some(IOSCommands::Ids) => {
                        args.push("--command");
                        args.push("ids");
                    }
                    _ => {}
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            Chrome {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec!["-t", "chrome", "-o", &*output_path.to_str().unwrap()];

                if *release {
                    args.push("-R");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            Win {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec!["-t", "win", "-o", &*output_path.to_str().unwrap()];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            #[cfg(target_os = "macos")]
            Mac {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                dry_run,
                build_mode: BuildMode { release, ci },
            } => {
                let mut args = vec!["-t", "mac", "-o", &*output_path.to_str().unwrap()];

                if *release {
                    args.push("-R");
                }

                if *dry_run {
                    args.push("-D");
                }

                if *ci {
                    args.push("--ci");
                }

                args.push(&*project_path.to_str().unwrap());
                args
            }
            Qr {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                layout,
            } => vec![
                "-t",
                "qr",
                "-o",
                &*output_path.to_str().unwrap(),
                "--command",
                layout,
                &*project_path.to_str().unwrap(),
            ],
            ErrorModel {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                layout,
            } => vec![
                "-t",
                "errormodel",
                "-o",
                &*output_path.to_str().unwrap(),
                "--command",
                layout,
                &*project_path.to_str().unwrap(),
            ],
            M17n { .. } |
            X11 { .. } => unreachable!("covered in previous match"),
        };

        if let Some(gh_username) = github_username {
            args.push("--github-username");
            args.push(&*gh_username);
        }

        if let Some(gh_token) = github_token {
            args.push("--github-token");
            args.push(&*gh_token);
        }

        args.push("--logging");
        args.push(logging);

        Ok(args)
    }
}

fn python_config() -> pyembed::PythonConfig {
    let mut config = default_python_config();

    let mod_language_tags = ExtensionModule {
        name: std::ffi::CString::new("language_tags").unwrap(),
        init_func: py_language_tags::PyInit_language_tags,
    };

    let mod_logger = ExtensionModule {
        name: std::ffi::CString::new("rust_logger").unwrap(),
        init_func: py_logger::PyInit_rust_logger,
    };

    let mod_reqwest = ExtensionModule {
        name: std::ffi::CString::new("reqwest").unwrap(),
        init_func: py_reqwest::PyInit_reqwest,
    };

    config.extra_extension_modules = vec![mod_language_tags, mod_logger, mod_reqwest];
    config
}

fn launch_py_kbdgen(args: &[&str]) -> i32 {
    // Load the default Python configuration as derived by the PyOxidizer config
    // file used at build time.
    let config = python_config();

    // Construct a new Python interpreter using that config, handling any errors
    // from construction.
    match MainPythonInterpreter::new(config) {
        Ok(mut interp) => {
            // And run it using the default run configuration as specified by the
            // configuration. If an uncaught Python exception is raised, handle it.
            // This includes the special SystemExit, which is a request to terminate the
            // process.
            let args = format!(
                "[{}]",
                args.iter()
                    .map(|x| format!("{:?}", x))
                    .collect::<Vec<_>>()
                    .join(", ")
            );
            match interp.run_code(&format!("import kbdgen.cli; kbdgen.cli.run_cli({})", args)) {
                Ok(_) => 0,
                Err(msg) => {
                    let py = interp.acquire_gil();
                    msg.print(py);
                    1
                }
            }
        }
        Err(msg) => {
            eprintln!("{}", msg);
            1
        }
    }
}

fn launch_repl() -> i32 {
    let config = python_config();
    match MainPythonInterpreter::new(config) {
        Ok(mut interp) => {
            match interp.run_repl() {
                Ok(_) => 0,
                Err(msg) => {
                    let py = interp.acquire_gil();
                    msg.print(py);
                    1
                }
            }
        }
        Err(msg) => {
            eprintln!("{}", msg);
            1
        }
    }
}

fn main() {
    env_logger::Builder::from_default_env()
        .filter(Some("kbdgen"), log::LevelFilter::Info)
        .target(env_logger::Target::Stderr)
        .init();

    let opt = Opts::from_args();

    let logging = match &*opt.logging {
        "trace" => log::Level::Trace,
        "debug" => log::Level::Debug,
        "info" => log::Level::Info,
        "warn" => log::Level::Warn,
        "error" => log::Level::Error,
        x => {
            eprintln!("Invalid logging level: {}", x);
            std::process::exit(1);
        }
    };

    std::env::set_var("RUST_LOG", logging.to_string());

    match opt.command {
        Commands::Build {
            github_username,
            github_token,
            command,
        } => match command {
            BuildCommands::X11 {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                build_mode: BuildMode { .. },
                standalone,
            } => kbdgen::cli::to_xkb::kbdgen_to_xkb(
                &project_path,
                &output_path,
                &kbdgen::cli::to_xkb::Options { standalone },
            )
            .unwrap(),
            BuildCommands::M17n {
                in_out:
                    InOutPaths {
                        output_path,
                        project_path,
                    },
                build_mode: BuildMode { .. },
            } => kbdgen::cli::to_m17n_mim::kbdgen_to_mim(
                &project_path,
                &output_path,
            )
            .unwrap(),
            command => match command.to_py_args(
                github_username.as_ref().map(|x| &**x),
                github_token.as_ref().map(|x| &**x),
                &opt.logging,
            ) {
                Ok(args) => std::process::exit(launch_py_kbdgen(&args)),
                Err(e) => {
                    eprintln!("{:?}", e);
                    std::process::exit(1);
                }
            },
        },

        Commands::New { command } => match command {
            NewCommands::Bundle {
                bundle_name,
                output_path,
            } => match kbdgen::cli::from_cldr::cldr_to_kbdgen(&*output_path, &*bundle_name) {
                Ok(_) => std::process::exit(0),
                Err(e) => {
                    eprintln!("{:?}", e);
                    std::process::exit(1);
                }
            },
            NewCommands::Layout { .. } => {
                eprintln!("Not yet supported.");
                std::process::exit(1)
            }
        },

        Commands::Repl => {
            std::process::exit(launch_repl())
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate regex;
extern crate xdg_basedir;

use std::collections;
use std::fs;
use std::io;
use std::io::BufRead;
use std::path;
use std::path::PathBuf;

fn get_rules_directory(options: Option<&str>) -> Result<PathBuf, String> {
    if let Some(directory) = options {
        Ok(PathBuf::from(directory))
    } else {
        let mut config_home_dir = try!(
            xdg_basedir::get_config_home().map_err(|e| e.to_string())
        );

        config_home_dir.push("q");
        config_home_dir.push("rules");

        Ok(config_home_dir)
    }
}

fn parse_rules_filenames(rules: &str, config_dir: &PathBuf) -> collections::HashSet<path::PathBuf> {
    rules
        .split(",")
        .map(
            |item| {
                let mut root_path = config_dir.clone();
                root_path.push(item);
                root_path
            }
        )
        .collect::<collections::HashSet<path::PathBuf>>()
}

fn parse_rules(filenames: &collections::HashSet<path::PathBuf>, case_insensitive: bool) -> Result<regex::Regex, String> {
    let mut regex_buffer: Vec<String> = Vec::with_capacity(filenames.len() * 2);

    for filename in filenames.iter() {
        let path = filename.as_path();
        let file = try!(fs::File::open(path).map_err(|e| e.to_string()));
        let reader = io::BufReader::new(file);

        for line in reader.lines() {
            match line {
                Ok(content) => {
                    let quoted_content = &regex::quote(&content);
                    regex_buffer.push(
                        if case_insensitive {
                            format!("(?i{})", quoted_content)
                        } else {
                            format!("({})", quoted_content)
                        }
                    )
                },
                Err(error) => return Err(
                    format!(
                        "Cannot fetch a line from file {}: {}!",
                        path.display(),
                        error.to_string()
                    )
                )
            }
        }
    }

    let compiled_result = regex::Regex::new(&regex_buffer.connect("|"));
    if let Err(e) = compiled_result {
        return Err(format!("Cannot compile regexps: {}", e.to_string()))
    }

    Ok(compiled_result.ok().unwrap())
}

fn get_rules(rules_directory: &PathBuf, rules_str: &str, case_insensitive: bool) -> Result<regex::Regex, String> {
    let filenames = parse_rules_filenames(rules_str, rules_directory);
    parse_rules(&filenames, case_insensitive)
}

fn main() {
    let options = clap::App::new("q")
        .author("Sergey Arkhipov <nineseconds@yandex.ru>")
        .version(&crate_version!()[..])
        .about("q is a gentle way to grep using predefined regexp sets.")
        .after_help("Please find more documentation at https://github.com/9seconds/q.")
        .arg(
            clap::Arg::with_name("SAME_LINE")
                .help("Keep matches on the same line")
                .short("l")
                .long("same_line")
        ).arg(
            clap::Arg::with_name("CASE_INSENSITIVE")
                .help("Use case insensitive regex versions.")
                .short("i")
                .long("case-insensitive")
        ).arg(
            clap::Arg::with_name("RULES_DIRECTORY")
                .help("Directory where rules could be found. By default it uses $XDG_CONFIG_HOME/q/rules")
                .short("-r")
                .long("rules")
                .takes_value(true)
        ).arg(
            clap::Arg::with_name("FILE")
                .help("File to process. Use '-' to read from stdin (default is stdin).")
                .short("-f")
                .long("file")
                .takes_value(true)
        ).arg(
            clap::Arg::with_name("RULES")
                .help("Regexp rules to apply to the stdin as a comma-separated list.")
                .index(1)
                .required(true)
        )
        .get_matches();

    let same_line = options.is_present("SAME_LINE");
    let case_insensitive = options.is_present("CASE_INSENSITIVE");
    let rules_directory = get_rules_directory(options.value_of("RULES_DIRECTORY"))
        .ok()
        .expect("Cannot determine rules directory!");
    let filename = options
        .value_of("FILE")
        .unwrap_or("-");

    let rules: regex::Regex;
    match get_rules(&rules_directory, options.value_of("RULES").unwrap(), case_insensitive) {
        Ok(result) => rules = result,
        Err(error) => panic!("Cannot parse rules: {}", error)
    }

    println!("Options: {}, {}, {}, {:?}", same_line, case_insensitive, filename, rules);
}
<commit_msg>Move funcs to modules<commit_after>#[macro_use]
extern crate clap;
extern crate regex;
extern crate xdg_basedir;
extern crate q;

fn main() {
    let options = clap::App::new("q")
        .author("Sergey Arkhipov <nineseconds@yandex.ru>")
        .version(&crate_version!()[..])
        .about("q is a gentle way to grep using predefined regexp sets.")
        .after_help("Please find more documentation at https://github.com/9seconds/q.")
        .arg(
            clap::Arg::with_name("SAME_LINE")
                .help("Keep matches on the same line")
                .short("l")
                .long("same_line")
        ).arg(
            clap::Arg::with_name("CASE_INSENSITIVE")
                .help("Use case insensitive regex versions.")
                .short("i")
                .long("case-insensitive")
        ).arg(
            clap::Arg::with_name("RULES_DIRECTORY")
                .help("Directory where rules could be found. By default it uses $XDG_CONFIG_HOME/q/rules")
                .short("-r")
                .long("rules")
                .takes_value(true)
        ).arg(
            clap::Arg::with_name("FILE")
                .help("File to process. Use '-' to read from stdin (default is stdin).")
                .short("-f")
                .long("file")
                .takes_value(true)
        ).arg(
            clap::Arg::with_name("RULES")
                .help("Regexp rules to apply to the stdin as a comma-separated list.")
                .index(1)
                .required(true)
        )
        .get_matches();

    let same_line = options.is_present("SAME_LINE");
    let case_insensitive = options.is_present("CASE_INSENSITIVE");
    let rules_directory = q::rules_parser::get_rules_directory(options.value_of("RULES_DIRECTORY"))
        .ok()
        .expect("Cannot determine rules directory!");
    let filename = options
        .value_of("FILE")
        .unwrap_or("-");

    let rules: regex::Regex;
    match q::rules_parser::get_rules(
        &rules_directory, options.value_of("RULES").unwrap(), case_insensitive
    ) {
        Ok(result) => rules = result,
        Err(error) => panic!("Cannot parse rules: {}", error)
    }

    println!("Options: {}, {}, {}, {:?}", same_line, case_insensitive, filename, rules);
}
<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>Add glium hello world example<commit_after>#[macro_use]
extern crate glium;

fn main() {
    use glium::{glutin, Surface};

    let mut events_loop = glutin::EventsLoop::new();
    let window = glutin::WindowBuilder::new();
    let context = glutin::ContextBuilder::new();
    let display = glium::Display::new(window, context, &events_loop).unwrap();

    #[derive(Copy, Clone)]
    struct Vertex {
        position: [f32; 2],
    }

    implement_vertex!(Vertex, position);

    let vertex1 = Vertex { position: [-0.5, -0.5] };
    let vertex2 = Vertex { position: [ 0.0,  0.5] };
    let vertex3 = Vertex { position: [ 0.5, -0.25] };
    let shape = vec![vertex1, vertex2, vertex3];

    let vertex_buffer = glium::VertexBuffer::new(&display, &shape).unwrap();
    let indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList);

    let vertex_shader_src = r#"
        #version 140
        in vec2 position;
        void main() {
            gl_Position = vec4(position, 0.0, 1.0);
        }
    "#;

    let fragment_shader_src = r#"
        #version 140
        out vec4 color;
        void main() {
            color = vec4(1.0, 0.0, 0.0, 1.0);
        }
    "#;

    let program = glium::Program::from_source(&display, vertex_shader_src, fragment_shader_src, None).unwrap();

    let mut closed = false;
    while !closed {
        let mut target = display.draw();
        target.clear_color(0.0, 0.0, 1.0, 1.0);
        target.draw(&vertex_buffer, &indices, &program, &glium::uniforms::EmptyUniforms,
                    &Default::default()).unwrap();
        target.finish().unwrap();

        events_loop.poll_events(|event| {
            match event {
                glutin::Event::WindowEvent { event, .. } => match event {
                    glutin::WindowEvent::Closed => closed = true,
                    _ => ()
                },
                _ => (),
            }
        });
    }
}
<|endoftext|>


<commit_before>#![allow(unused_parens)]
#![feature(custom_derive, plugin)] // necessary for serde
#![plugin(serde_macros)]
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;

extern crate iron ;
extern crate staticfile;
extern crate mount;
extern crate router;
extern crate bodyparser;

extern crate mpv ;
extern crate sdl2;
extern crate sdl2_ttf;
extern crate sdl2_image;
extern crate sdl2_sys;
#[macro_use]
extern crate log;
extern crate env_logger;
#[macro_use]
extern crate clap;
use clap::{Arg, App};

mod utils;
mod display;
mod init;
mod subtitles;
mod toyunda_player;

use toyunda_player::ToyundaPlayer;
use std::os::raw::c_void;

fn main() {
    // init the logger
    env_logger::init().unwrap();

    let matches = App::new("Toyunda Player")
                          .version(crate_version!())
                          .after_help("PLAYER SHORTCUTS :\n    \
                            * V : Hides / Shows subtitles\n    \
                            * F : Toggles fullscreen\n    \
                            * + / - : Raises / Lowers the volume\n    \
                            * Left / Right arrow : Seek backwards / frontwards\n    \
                            ")
                          .author("Cobrand")
                          .about("A flexible karaoke player for the epitanime association")
                          .arg(Arg::with_name("karaoke_mode")
                            .short("k")
                            .long("karaoke")
                            .help("Sets mode to karaoke mode"))
                          .arg(Arg::with_name("edit_mode")
                            .short("e")
                            .long("edit")
                            .help("Sets mode to edit mode")
                            .conflicts_with("karaoke_mode"))
                          .arg(Arg::with_name("fullscreen")
                            .short("f")
                            .long("fullscreen")
                            .help("Enables fullscreen"))
                          .arg(Arg::with_name("volume")
                            .short("v")
                            .long("volume")
                            .takes_value(true)
                            .help("Initial volume of the player; Default is 100"))
                          .arg(Arg::with_name("quit")
                            .short("q")
                            .long("quit")
                            .help("Forces quiting the player once the waiting queue is finished"))
                          .arg(Arg::with_name("no-quit")
                            .long("no-quit")
                            .conflicts_with("quit")
                            .help("Forces keeping alive the player once the waiting queue is finished"))
                          .arg(Arg::with_name("VIDEO_FILE")
                            .help("Sets the video file(s) to play")
                            .multiple(true))
                          .get_matches();

    // INIT SDL
    let sdl_context = sdl2::init().unwrap();
    let mut video_subsystem = sdl_context.video().unwrap();
    let renderer = init::init_sdl(&mut video_subsystem,&matches);
    let video_subsystem_ptr = &mut video_subsystem as *mut _ as *mut c_void;
    // INIT MPV
    let mut mpv_builder = mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
    mpv_builder.set_option("sid", "no").unwrap(); // disables subtitles if any
    mpv_builder.set_option("softvol", "yes").unwrap(); // enables softvol so it can go higher than 100%
    mpv_builder.set_option("softvol-max", 250.0).unwrap(); // makes the max volume at 250%
    mpv_builder.try_hardware_decoding().unwrap(); // try hardware decoding instead of software decoding
    let mpv = mpv_builder.build_with_gl(Some(init::get_proc_address), video_subsystem_ptr)
       .expect("Error while initializing MPV");
    // BIND MPV WITH SDL

    let displayer = display::Displayer::new(renderer)
                        .expect("Failed to init displayer");

    if matches.is_present("karaoke_mode") {
        let mouse_utils = sdl_context.mouse();
        mouse_utils.show_cursor(false);
        // dont show cursor on top of player in karaoke mode
    }
    // Create a new displayer for the toyunda_player

    let mut toyunda_player = ToyundaPlayer::new(mpv, displayer);
    match toyunda_player.start(matches) {
        Err(e) => {
            error!("Failed to start player with given arguments, expect default parameters !\n\
                    '{}' ({:?})",e,e);
        }
        Ok(_) => {
            info!("Parsed arguments successfully");
        }
    };
    let res = toyunda_player.main_loop(&sdl_context);
    match res {
        Ok(_) => {
            info!("Toyunda Player finished gracefully");
        },
        Err(e) => {
            error!("An uncoverable error occured : {}",e);
        }
    };
}
<commit_msg>Added -d option + demoved use of delim ',' for files<commit_after>#![allow(unused_parens)]
#![feature(custom_derive, plugin)] // necessary for serde
#![plugin(serde_macros)]
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;

extern crate iron ;
extern crate staticfile;
extern crate mount;
extern crate router;
extern crate bodyparser;

extern crate mpv ;
extern crate sdl2;
extern crate sdl2_ttf;
extern crate sdl2_image;
extern crate sdl2_sys;
#[macro_use]
extern crate log;
extern crate env_logger;
#[macro_use]
extern crate clap;
use clap::{Arg, App};

mod utils;
mod display;
mod init;
mod subtitles;
mod toyunda_player;

use toyunda_player::ToyundaPlayer;
use std::os::raw::c_void;

fn main() {
    // init the logger
    env_logger::init().unwrap();

    let matches = App::new("Toyunda Player")
                          .version(crate_version!())
                          .after_help("PLAYER SHORTCUTS :\n    \
                            * V : Hides / Shows subtitles\n    \
                            * F : Toggles fullscreen\n    \
                            * + / - : Raises / Lowers the volume\n    \
                            * Left / Right arrow : Seek backwards / frontwards\n    \
                            ")
                          .author("Cobrand")
                          .about("A flexible karaoke player for the epitanime association")
                          .arg(Arg::with_name("karaoke_mode")
                            .short("k")
                            .long("karaoke")
                            .help("Sets mode to karaoke mode"))
                          .arg(Arg::with_name("edit_mode")
                            .short("e")
                            .long("edit")
                            .help("Sets mode to edit mode")
                            .conflicts_with("karaoke_mode"))
                          .arg(Arg::with_name("fullscreen")
                            .short("f")
                            .long("fullscreen")
                            .help("Enables fullscreen"))
                          .arg(Arg::with_name("yaml_directory")
                            .short("d")
                            .long("directory")
                            .takes_value(true)
                            .help("Where to look the yaml files at")
                            .requires("karaoke_mode"))
                          .arg(Arg::with_name("volume")
                            .short("v")
                            .long("volume")
                            .takes_value(true)
                            .help("Initial volume of the player; Default is 100"))
                          .arg(Arg::with_name("quit")
                            .short("q")
                            .long("quit")
                            .help("Forces quiting the player once the waiting queue is finished"))
                          .arg(Arg::with_name("no-quit")
                            .long("no-quit")
                            .conflicts_with("quit")
                            .help("Forces keeping alive the player once the waiting queue is finished"))
                          .arg(Arg::with_name("VIDEO_FILE")
                            .help("Sets the video file(s) to play")
                            .use_delimiter(false)
                            .multiple(true))
                          .get_matches();

    // INIT SDL
    let sdl_context = sdl2::init().unwrap();
    let mut video_subsystem = sdl_context.video().unwrap();
    let renderer = init::init_sdl(&mut video_subsystem,&matches);
    let video_subsystem_ptr = &mut video_subsystem as *mut _ as *mut c_void;
    // INIT MPV
    let mut mpv_builder = mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
    mpv_builder.set_option("sid", "no").unwrap(); // disables subtitles if any
    mpv_builder.set_option("softvol", "yes").unwrap(); // enables softvol so it can go higher than 100%
    mpv_builder.set_option("softvol-max", 250.0).unwrap(); // makes the max volume at 250%
    mpv_builder.try_hardware_decoding().unwrap(); // try hardware decoding instead of software decoding
    let mpv = mpv_builder.build_with_gl(Some(init::get_proc_address), video_subsystem_ptr)
       .expect("Error while initializing MPV");
    // BIND MPV WITH SDL

    let displayer = display::Displayer::new(renderer)
                        .expect("Failed to init displayer");

    if matches.is_present("karaoke_mode") {
        let mouse_utils = sdl_context.mouse();
        mouse_utils.show_cursor(false);
        // dont show cursor on top of player in karaoke mode
    }
    // Create a new displayer for the toyunda_player

    let mut toyunda_player = ToyundaPlayer::new(mpv, displayer);
    match toyunda_player.start(matches) {
        Err(e) => {
            error!("Failed to start player with given arguments, expect default parameters !\n\
                    '{}' ({:?})",e,e);
        }
        Ok(_) => {
            info!("Parsed arguments successfully");
        }
    };
    let res = toyunda_player.main_loop(&sdl_context);
    match res {
        Ok(_) => {
            info!("Toyunda Player finished gracefully");
        },
        Err(e) => {
            error!("An uncoverable error occured : {}",e);
        }
    };
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate git2;

use git2::{Repository, Commit, Oid, Revwalk, Index};
use std::collections::HashMap;

#[macro_use]
mod macros;

const E_SUCCESS: i32 = 0;
const E_NO_GIT_REPO: i32 = 1;
const E_FOUND_DANGLING_REFERENCES: i32 = 2;

fn main() {
    let exit_code = real_main();
    std::process::exit(exit_code);
}

fn real_main() -> i32 {
    let submodule_dir = parse_cli_arguments();
    println!("Merging {}...", submodule_dir);

    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(e) => {
            eprintln!("Couldn't find Git repo in the current directory: {}",
                      e.message());
            return E_NO_GIT_REPO;
        }
    };

    let mut submodule_revwalk = get_submodule_revwalk(&repo, &submodule_dir);

    let mut old_id_to_new = HashMap::new();

    rewrite_submodule_history(&repo,
                              &mut submodule_revwalk,
                              &mut old_id_to_new,
                              &submodule_dir);

    match find_dangling_references_to_submodule(&repo, &submodule_dir, &old_id_to_new) {
        Some(_) => return E_FOUND_DANGLING_REFERENCES,
        None => {}
    }

    let mut repo_revwalk = get_repo_revwalk(&repo);
    rewrite_repo_history(&repo, &mut repo_revwalk, &mut old_id_to_new, &submodule_dir);

    checkout_rewritten_history(&repo, &old_id_to_new);

    E_SUCCESS
}

fn parse_cli_arguments() -> String {
    let options = clap::App::new("git-submerge")
        .version("0.1")
        .author("Alexander Batischev <eual.jp@gmail.com>")
        .about("Merge Git submodule into the main repo as if they've never been separate at all")
        .arg(clap::Arg::with_name("SUBMODULE_DIR")
            .help("The submodule to merge")
            .required(true)
            .index(1))
        .get_matches();

    // We can safely use unwrap() here because the argument is marked as "required" and Clap checks
    // its presence for us.
    String::from(options.value_of("SUBMODULE_DIR").unwrap())
}

fn get_submodule_revwalk<'repo>(repo: &'repo Repository, submodule_dir: &str) -> Revwalk<'repo> {
    let submodule_url = String::from("./") + submodule_dir;
    let mut remote = repo.remote_anonymous(&submodule_url)
        .expect("Couldn't create an anonymous remote");
    remote.fetch(&[], None, None).expect("Couldn't fetch submodule's history");
    let submodule = repo.find_submodule(submodule_dir)
        .expect("Couldn't find the submodule with expected path");
    let submodule_head = submodule.head_id()
        .expect("Couldn't obtain submodule's HEAD");

    let mut revwalk = repo.revwalk().expect("Couldn't obtain RevWalk object for the repo");
    // "Topological" and reverse means "parents are always visited before their children".
    // We need that in order to be sure that our old-to-new-ids map always contains everything we
    // need it to contain.
    revwalk.set_sorting(git2::SORT_REVERSE | git2::SORT_TOPOLOGICAL);
    // TODO (#6): push all branches and tags, not just HEAD
    revwalk.push(submodule_head).expect("Couldn't add submodule's HEAD to RevWalk list");

    revwalk
}

fn rewrite_submodule_history(repo: &Repository,
                             revwalk: &mut Revwalk,
                             old_id_to_new: &mut HashMap<Oid, Oid>,
                             submodule_dir: &str) {
    for maybe_oid in revwalk {
        match maybe_oid {
            Ok(oid) => {
                let commit = repo.find_commit(oid)
                    .expect(&format!("Couldn't get a commit with ID {}", oid));
                let tree = commit.tree()
                    .expect(&format!("Couldn't obtain the tree of a commit with ID {}", oid));
                let mut old_index = Index::new()
                    .expect("Couldn't create an in-memory index for commit");
                let mut new_index = Index::new().expect("Couldn't create an in-memory index");
                old_index.read_tree(&tree)
                    .expect(&format!("Couldn't read the commit {} into index", oid));

                // Obtain the new tree, where everything from the old one is moved under
                // a directory named after the submodule
                for entry in old_index.iter() {
                    let mut new_entry = entry;

                    let mut new_path = String::from(submodule_dir);
                    new_path += "/";
                    new_path += &String::from_utf8(new_entry.path)
                        .expect("Failed to convert a path to str");

                    new_entry.path = new_path.into_bytes();
                    new_index.add(&new_entry).expect("Couldn't add an entry to the index");
                }
                let tree_id = new_index.write_tree_to(&repo)
                    .expect("Couldn't write the index into a tree");
                old_id_to_new.insert(tree.id(), tree_id);
                let tree = repo.find_tree(tree_id)
                    .expect("Couldn't retrieve the tree we just created");

                let parents = {
                    let mut p: Vec<Commit> = Vec::new();
                    for parent_id in commit.parent_ids() {
                        let new_parent_id = old_id_to_new[&parent_id];
                        let parent = repo.find_commit(new_parent_id)
                            .expect("Couldn't find parent commit by its id");
                        p.push(parent);
                    }
                    p
                };

                let mut parents_refs: Vec<&Commit> = Vec::new();
                for i in 0..parents.len() {
                    parents_refs.push(&parents[i]);
                }
                let new_commit_id = repo.commit(None,
                            &commit.author(),
                            &commit.committer(),
                            &commit.message().expect("Couldn't retrieve commit's message"),
                            &tree,
                            &parents_refs[..])
                    .expect("Failed to commit");

                old_id_to_new.insert(oid, new_commit_id);
            }
            Err(e) => eprintln!("Error walking the submodule's history: {:?}", e),
        }
    }
}

fn find_dangling_references_to_submodule<'repo>(repo: &'repo Repository,
                                                submodule_dir: &str,
                                                old_id_to_new: &HashMap<Oid, Oid>)
                                                -> Option<bool> {
    let submodule_path = std::path::Path::new(submodule_dir);

    let known_submodule_commits: std::collections::HashSet<&Oid> = old_id_to_new.keys().collect();
    let mut dangling_references = std::collections::HashSet::new();

    let revwalk = get_repo_revwalk(&repo);

    for maybe_oid in revwalk {
        match maybe_oid {
            Ok(oid) => {
                let commit = repo.find_commit(oid)
                    .expect(&format!("Couldn't get a commit with ID {}", oid));
                let tree = commit.tree()
                    .expect(&format!("Couldn't obtain the tree of a commit with ID {}", oid));

                let submodule_subdir = match tree.get_path(submodule_path) {
                    Ok(tree) => tree,
                    Err(e) => {
                        if e.code() == git2::ErrorCode::NotFound &&
                           e.class() == git2::ErrorClass::Tree {
                            // It's okay. The tree lacks the subtree corresponding to the
                            // submodule. In other words, the commit doesn't include the submodule.
                            // That's totally fine. Let's  move on.
                            continue;
                        } else {
                            // Unexpected error; let's report it and abort the program
                            panic!("Error getting submodule's subdir from the tree: {:?}", e);
                        };
                    }
                };

                // **INVARIANT**: if we got this far, current commit contains a submodule and
                // should be rewritten

                let submodule_commit_id = submodule_subdir.id();
                if !known_submodule_commits.contains(&submodule_commit_id) {
                    dangling_references.insert(submodule_commit_id);
                }
            }
            Err(e) => eprintln!("Error walking the submodule's history: {:?}", e),
        }
    }

    if dangling_references.is_empty() {
        None
    } else {
        // TODO (#16): provide hints as to what options user has to resolve this
        eprintln!("The repository references the following submodule commits, but they couldn't \
                   be found in the submodule's history:");
        for id in dangling_references {
            eprintln!("{}", id);
        }

        Some(true)
    }
}

fn get_repo_revwalk<'repo>(repo: &'repo Repository) -> Revwalk<'repo> {
    let mut revwalk = repo.revwalk().expect("Couldn't obtain RevWalk object for the repo");
    revwalk.set_sorting(git2::SORT_REVERSE | git2::SORT_TOPOLOGICAL);
    let head = repo.head().expect("Couldn't obtain repo's HEAD");
    let head_id = head.target().expect("Couldn't resolve repo's HEAD to a commit ID");
    // TODO (#6): push all branches and tags, not just HEAD
    revwalk.push(head_id).expect("Couldn't add repo's HEAD to RevWalk list");

    revwalk
}

fn rewrite_repo_history(repo: &Repository,
                        revwalk: &mut Revwalk,
                        old_id_to_new: &mut HashMap<Oid, Oid>,
                        submodule_dir: &str) {
    let submodule_path = std::path::Path::new(submodule_dir);

    for maybe_oid in revwalk {
        match maybe_oid {
            Ok(oid) => {
                let commit = repo.find_commit(oid)
                    .expect(&format!("Couldn't get a commit with ID {}", oid));
                let tree = commit.tree()
                    .expect(&format!("Couldn't obtain the tree of a commit with ID {}", oid));

                let submodule_subdir = match tree.get_path(submodule_path) {
                    Ok(tree) => tree,
                    Err(e) => {
                        if e.code() == git2::ErrorCode::NotFound &&
                           e.class() == git2::ErrorClass::Tree {
                            // It's okay. The tree lacks the subtree corresponding to the
                            // submodule. In other words, the commit doesn't include the submodule.
                            // That's totally fine. Let's map it into itself and move on.
                            old_id_to_new.insert(oid, oid);
                            continue;
                        } else {
                            // Unexpected error; let's report it and abort the program
                            panic!("Error getting submodule's subdir from the tree: {:?}", e);
                        };
                    }
                };

                // **INVARIANT**: if we got this far, current commit contains a submodule and
                // should be rewritten

                let submodule_commit_id = submodule_subdir.id();
                let new_submodule_commit_id = old_id_to_new[&submodule_commit_id];
                let submodule_commit = repo.find_commit(new_submodule_commit_id)
                    .expect("Couldn't obtain submodule's commit");
                let subtree_id = submodule_commit.tree()
                    .and_then(|t| t.get_path(submodule_path))
                    .and_then(|te| Ok(te.id()))
                    .expect("Couldn't obtain submodule's subtree ID");

                let mut treebuilder = repo.treebuilder(Some(&tree))
                    .expect("Couldn't create TreeBuilder");
                treebuilder.remove(submodule_path)
                    .expect("Couldn't remove submodule path from TreeBuilder");
                treebuilder.insert(submodule_path, subtree_id, 0o040000)
                    .expect("Couldn't add submodule as a subdir to TreeBuilder");
                let new_tree_id = treebuilder.write()
                    .expect("Couldn't write TreeBuilder into a Tree");
                let new_tree = repo.find_tree(new_tree_id)
                    .expect("Couldn't read back the Tree we just wrote");

                // In commits that used to update the submodule, add a parent pointing to
                // appropriate commit in new submodule history
                let mut parent_subtree_ids = std::collections::HashSet::new();
                for parent in commit.parents() {
                    let parent_tree = parent.tree().expect("Couldn't obtain parent's tree");
                    let parent_subdir_tree_id = parent_tree.get_path(submodule_path)
                        .and_then(|x| Ok(x.id()));

                    match parent_subdir_tree_id {
                        Ok(id) => {
                            parent_subtree_ids.insert(id);
                            ()
                        }
                        Err(e) => {
                            if e.code() == git2::ErrorCode::NotFound &&
                               e.class() == git2::ErrorClass::Tree {
                                continue;
                            } else {
                                panic!("Error getting submodule's subdir from the tree: {:?}", e);
                            };
                        }
                    }
                }

                // Here's a few pictures to help you understand how we figure out if current commit
                // updated the submodule. If we draw a DAG and name submodule states, the following
                // situations will mean that the submodule wasn't updated:
                //
                //     o--o--o--A--
                //                 `,-A
                //      o--o--o--B-
                //
                // or
                //
                //     o--o--o--A--
                //                 `,-B
                //      o--o--o--B-
                //
                // And in the following graphs the submodule was updated:
                //
                //     o--o--o--A--
                //                 `,-C
                //      o--o--o--B-
                //
                // or
                //
                //     o--o--o--o--A--B
                //
                // Put into words, the rule will be "the submodule state in current commit is
                // different from states in all its parents". Or, more formally, the current state
                // doesn't belong to the set of states in parents.
                let submodule_updated: bool = !parent_subtree_ids.contains(&submodule_commit_id);

                // Rewrite the parents if the submodule was updated
                let parents = {
                    let mut p: Vec<Commit> = Vec::new();
                    for parent_id in commit.parent_ids() {
                        let actual_parent_id = old_id_to_new[&parent_id];
                        let parent = repo.find_commit(actual_parent_id)
                            .expect("Couldn't find parent commit by its id");
                        p.push(parent);
                    }

                    if submodule_updated {
                        p.push(submodule_commit);
                    }

                    p
                };

                let mut parents_refs: Vec<&Commit> = Vec::new();
                for i in 0..parents.len() {
                    parents_refs.push(&parents[i]);
                }
                let new_commit_id = repo.commit(None,
                            &commit.author(),
                            &commit.committer(),
                            &commit.message().expect("Couldn't retrieve commit's message"),
                            &new_tree,
                            &parents_refs[..])
                    .expect("Failed to commit");

                old_id_to_new.insert(oid, new_commit_id);
            }
            Err(e) => eprintln!("Error walking the repo's history: {:?}", e),
        }
    }
}

fn checkout_rewritten_history(repo: &Repository, old_id_to_new: &HashMap<Oid, Oid>) {
    let mut checkoutbuilder = git2::build::CheckoutBuilder::new();
    checkoutbuilder.force();

    let head = repo.head().expect("Couldn't obtain repo's HEAD");
    let head_id = head.target().expect("Couldn't resolve repo's HEAD to a commit ID");
    let updated_id = old_id_to_new[&head_id];
    let object = repo.find_object(updated_id, None)
        .expect("Couldn't look up an object at which HEAD points");
    repo.reset(&object, git2::ResetType::Hard, Some(&mut checkoutbuilder))
        .expect("Couldn't run force-reset");
}
<commit_msg>Pull authors list from Cargo.toml<commit_after>#[macro_use]
extern crate clap;
extern crate git2;

use git2::{Repository, Commit, Oid, Revwalk, Index};
use std::collections::HashMap;

#[macro_use]
mod macros;

const E_SUCCESS: i32 = 0;
const E_NO_GIT_REPO: i32 = 1;
const E_FOUND_DANGLING_REFERENCES: i32 = 2;

fn main() {
    let exit_code = real_main();
    std::process::exit(exit_code);
}

fn real_main() -> i32 {
    let submodule_dir = parse_cli_arguments();
    println!("Merging {}...", submodule_dir);

    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(e) => {
            eprintln!("Couldn't find Git repo in the current directory: {}",
                      e.message());
            return E_NO_GIT_REPO;
        }
    };

    let mut submodule_revwalk = get_submodule_revwalk(&repo, &submodule_dir);

    let mut old_id_to_new = HashMap::new();

    rewrite_submodule_history(&repo,
                              &mut submodule_revwalk,
                              &mut old_id_to_new,
                              &submodule_dir);

    match find_dangling_references_to_submodule(&repo, &submodule_dir, &old_id_to_new) {
        Some(_) => return E_FOUND_DANGLING_REFERENCES,
        None => {}
    }

    let mut repo_revwalk = get_repo_revwalk(&repo);
    rewrite_repo_history(&repo, &mut repo_revwalk, &mut old_id_to_new, &submodule_dir);

    checkout_rewritten_history(&repo, &old_id_to_new);

    E_SUCCESS
}

fn parse_cli_arguments() -> String {
    let options = clap::App::new("git-submerge")
        .version("0.1")
        .author(crate_authors!())
        .about("Merge Git submodule into the main repo as if they've never been separate at all")
        .arg(clap::Arg::with_name("SUBMODULE_DIR")
            .help("The submodule to merge")
            .required(true)
            .index(1))
        .get_matches();

    // We can safely use unwrap() here because the argument is marked as "required" and Clap checks
    // its presence for us.
    String::from(options.value_of("SUBMODULE_DIR").unwrap())
}

fn get_submodule_revwalk<'repo>(repo: &'repo Repository, submodule_dir: &str) -> Revwalk<'repo> {
    let submodule_url = String::from("./") + submodule_dir;
    let mut remote = repo.remote_anonymous(&submodule_url)
        .expect("Couldn't create an anonymous remote");
    remote.fetch(&[], None, None).expect("Couldn't fetch submodule's history");
    let submodule = repo.find_submodule(submodule_dir)
        .expect("Couldn't find the submodule with expected path");
    let submodule_head = submodule.head_id()
        .expect("Couldn't obtain submodule's HEAD");

    let mut revwalk = repo.revwalk().expect("Couldn't obtain RevWalk object for the repo");
    // "Topological" and reverse means "parents are always visited before their children".
    // We need that in order to be sure that our old-to-new-ids map always contains everything we
    // need it to contain.
    revwalk.set_sorting(git2::SORT_REVERSE | git2::SORT_TOPOLOGICAL);
    // TODO (#6): push all branches and tags, not just HEAD
    revwalk.push(submodule_head).expect("Couldn't add submodule's HEAD to RevWalk list");

    revwalk
}

fn rewrite_submodule_history(repo: &Repository,
                             revwalk: &mut Revwalk,
                             old_id_to_new: &mut HashMap<Oid, Oid>,
                             submodule_dir: &str) {
    for maybe_oid in revwalk {
        match maybe_oid {
            Ok(oid) => {
                let commit = repo.find_commit(oid)
                    .expect(&format!("Couldn't get a commit with ID {}", oid));
                let tree = commit.tree()
                    .expect(&format!("Couldn't obtain the tree of a commit with ID {}", oid));
                let mut old_index = Index::new()
                    .expect("Couldn't create an in-memory index for commit");
                let mut new_index = Index::new().expect("Couldn't create an in-memory index");
                old_index.read_tree(&tree)
                    .expect(&format!("Couldn't read the commit {} into index", oid));

                // Obtain the new tree, where everything from the old one is moved under
                // a directory named after the submodule
                for entry in old_index.iter() {
                    let mut new_entry = entry;

                    let mut new_path = String::from(submodule_dir);
                    new_path += "/";
                    new_path += &String::from_utf8(new_entry.path)
                        .expect("Failed to convert a path to str");

                    new_entry.path = new_path.into_bytes();
                    new_index.add(&new_entry).expect("Couldn't add an entry to the index");
                }
                let tree_id = new_index.write_tree_to(&repo)
                    .expect("Couldn't write the index into a tree");
                old_id_to_new.insert(tree.id(), tree_id);
                let tree = repo.find_tree(tree_id)
                    .expect("Couldn't retrieve the tree we just created");

                let parents = {
                    let mut p: Vec<Commit> = Vec::new();
                    for parent_id in commit.parent_ids() {
                        let new_parent_id = old_id_to_new[&parent_id];
                        let parent = repo.find_commit(new_parent_id)
                            .expect("Couldn't find parent commit by its id");
                        p.push(parent);
                    }
                    p
                };

                let mut parents_refs: Vec<&Commit> = Vec::new();
                for i in 0..parents.len() {
                    parents_refs.push(&parents[i]);
                }
                let new_commit_id = repo.commit(None,
                            &commit.author(),
                            &commit.committer(),
                            &commit.message().expect("Couldn't retrieve commit's message"),
                            &tree,
                            &parents_refs[..])
                    .expect("Failed to commit");

                old_id_to_new.insert(oid, new_commit_id);
            }
            Err(e) => eprintln!("Error walking the submodule's history: {:?}", e),
        }
    }
}

fn find_dangling_references_to_submodule<'repo>(repo: &'repo Repository,
                                                submodule_dir: &str,
                                                old_id_to_new: &HashMap<Oid, Oid>)
                                                -> Option<bool> {
    let submodule_path = std::path::Path::new(submodule_dir);

    let known_submodule_commits: std::collections::HashSet<&Oid> = old_id_to_new.keys().collect();
    let mut dangling_references = std::collections::HashSet::new();

    let revwalk = get_repo_revwalk(&repo);

    for maybe_oid in revwalk {
        match maybe_oid {
            Ok(oid) => {
                let commit = repo.find_commit(oid)
                    .expect(&format!("Couldn't get a commit with ID {}", oid));
                let tree = commit.tree()
                    .expect(&format!("Couldn't obtain the tree of a commit with ID {}", oid));

                let submodule_subdir = match tree.get_path(submodule_path) {
                    Ok(tree) => tree,
                    Err(e) => {
                        if e.code() == git2::ErrorCode::NotFound &&
                           e.class() == git2::ErrorClass::Tree {
                            // It's okay. The tree lacks the subtree corresponding to the
                            // submodule. In other words, the commit doesn't include the submodule.
                            // That's totally fine. Let's  move on.
                            continue;
                        } else {
                            // Unexpected error; let's report it and abort the program
                            panic!("Error getting submodule's subdir from the tree: {:?}", e);
                        };
                    }
                };

                // **INVARIANT**: if we got this far, current commit contains a submodule and
                // should be rewritten

                let submodule_commit_id = submodule_subdir.id();
                if !known_submodule_commits.contains(&submodule_commit_id) {
                    dangling_references.insert(submodule_commit_id);
                }
            }
            Err(e) => eprintln!("Error walking the submodule's history: {:?}", e),
        }
    }

    if dangling_references.is_empty() {
        None
    } else {
        // TODO (#16): provide hints as to what options user has to resolve this
        eprintln!("The repository references the following submodule commits, but they couldn't \
                   be found in the submodule's history:");
        for id in dangling_references {
            eprintln!("{}", id);
        }

        Some(true)
    }
}

fn get_repo_revwalk<'repo>(repo: &'repo Repository) -> Revwalk<'repo> {
    let mut revwalk = repo.revwalk().expect("Couldn't obtain RevWalk object for the repo");
    revwalk.set_sorting(git2::SORT_REVERSE | git2::SORT_TOPOLOGICAL);
    let head = repo.head().expect("Couldn't obtain repo's HEAD");
    let head_id = head.target().expect("Couldn't resolve repo's HEAD to a commit ID");
    // TODO (#6): push all branches and tags, not just HEAD
    revwalk.push(head_id).expect("Couldn't add repo's HEAD to RevWalk list");

    revwalk
}

fn rewrite_repo_history(repo: &Repository,
                        revwalk: &mut Revwalk,
                        old_id_to_new: &mut HashMap<Oid, Oid>,
                        submodule_dir: &str) {
    let submodule_path = std::path::Path::new(submodule_dir);

    for maybe_oid in revwalk {
        match maybe_oid {
            Ok(oid) => {
                let commit = repo.find_commit(oid)
                    .expect(&format!("Couldn't get a commit with ID {}", oid));
                let tree = commit.tree()
                    .expect(&format!("Couldn't obtain the tree of a commit with ID {}", oid));

                let submodule_subdir = match tree.get_path(submodule_path) {
                    Ok(tree) => tree,
                    Err(e) => {
                        if e.code() == git2::ErrorCode::NotFound &&
                           e.class() == git2::ErrorClass::Tree {
                            // It's okay. The tree lacks the subtree corresponding to the
                            // submodule. In other words, the commit doesn't include the submodule.
                            // That's totally fine. Let's map it into itself and move on.
                            old_id_to_new.insert(oid, oid);
                            continue;
                        } else {
                            // Unexpected error; let's report it and abort the program
                            panic!("Error getting submodule's subdir from the tree: {:?}", e);
                        };
                    }
                };

                // **INVARIANT**: if we got this far, current commit contains a submodule and
                // should be rewritten

                let submodule_commit_id = submodule_subdir.id();
                let new_submodule_commit_id = old_id_to_new[&submodule_commit_id];
                let submodule_commit = repo.find_commit(new_submodule_commit_id)
                    .expect("Couldn't obtain submodule's commit");
                let subtree_id = submodule_commit.tree()
                    .and_then(|t| t.get_path(submodule_path))
                    .and_then(|te| Ok(te.id()))
                    .expect("Couldn't obtain submodule's subtree ID");

                let mut treebuilder = repo.treebuilder(Some(&tree))
                    .expect("Couldn't create TreeBuilder");
                treebuilder.remove(submodule_path)
                    .expect("Couldn't remove submodule path from TreeBuilder");
                treebuilder.insert(submodule_path, subtree_id, 0o040000)
                    .expect("Couldn't add submodule as a subdir to TreeBuilder");
                let new_tree_id = treebuilder.write()
                    .expect("Couldn't write TreeBuilder into a Tree");
                let new_tree = repo.find_tree(new_tree_id)
                    .expect("Couldn't read back the Tree we just wrote");

                // In commits that used to update the submodule, add a parent pointing to
                // appropriate commit in new submodule history
                let mut parent_subtree_ids = std::collections::HashSet::new();
                for parent in commit.parents() {
                    let parent_tree = parent.tree().expect("Couldn't obtain parent's tree");
                    let parent_subdir_tree_id = parent_tree.get_path(submodule_path)
                        .and_then(|x| Ok(x.id()));

                    match parent_subdir_tree_id {
                        Ok(id) => {
                            parent_subtree_ids.insert(id);
                            ()
                        }
                        Err(e) => {
                            if e.code() == git2::ErrorCode::NotFound &&
                               e.class() == git2::ErrorClass::Tree {
                                continue;
                            } else {
                                panic!("Error getting submodule's subdir from the tree: {:?}", e);
                            };
                        }
                    }
                }

                // Here's a few pictures to help you understand how we figure out if current commit
                // updated the submodule. If we draw a DAG and name submodule states, the following
                // situations will mean that the submodule wasn't updated:
                //
                //     o--o--o--A--
                //                 `,-A
                //      o--o--o--B-
                //
                // or
                //
                //     o--o--o--A--
                //                 `,-B
                //      o--o--o--B-
                //
                // And in the following graphs the submodule was updated:
                //
                //     o--o--o--A--
                //                 `,-C
                //      o--o--o--B-
                //
                // or
                //
                //     o--o--o--o--A--B
                //
                // Put into words, the rule will be "the submodule state in current commit is
                // different from states in all its parents". Or, more formally, the current state
                // doesn't belong to the set of states in parents.
                let submodule_updated: bool = !parent_subtree_ids.contains(&submodule_commit_id);

                // Rewrite the parents if the submodule was updated
                let parents = {
                    let mut p: Vec<Commit> = Vec::new();
                    for parent_id in commit.parent_ids() {
                        let actual_parent_id = old_id_to_new[&parent_id];
                        let parent = repo.find_commit(actual_parent_id)
                            .expect("Couldn't find parent commit by its id");
                        p.push(parent);
                    }

                    if submodule_updated {
                        p.push(submodule_commit);
                    }

                    p
                };

                let mut parents_refs: Vec<&Commit> = Vec::new();
                for i in 0..parents.len() {
                    parents_refs.push(&parents[i]);
                }
                let new_commit_id = repo.commit(None,
                            &commit.author(),
                            &commit.committer(),
                            &commit.message().expect("Couldn't retrieve commit's message"),
                            &new_tree,
                            &parents_refs[..])
                    .expect("Failed to commit");

                old_id_to_new.insert(oid, new_commit_id);
            }
            Err(e) => eprintln!("Error walking the repo's history: {:?}", e),
        }
    }
}

fn checkout_rewritten_history(repo: &Repository, old_id_to_new: &HashMap<Oid, Oid>) {
    let mut checkoutbuilder = git2::build::CheckoutBuilder::new();
    checkoutbuilder.force();

    let head = repo.head().expect("Couldn't obtain repo's HEAD");
    let head_id = head.target().expect("Couldn't resolve repo's HEAD to a commit ID");
    let updated_id = old_id_to_new[&head_id];
    let object = repo.find_object(updated_id, None)
        .expect("Couldn't look up an object at which HEAD points");
    repo.reset(&object, git2::ResetType::Hard, Some(&mut checkoutbuilder))
        .expect("Couldn't run force-reset");
}
<|endoftext|>