aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/command/global_command.rs
blob: 5dc22a3f5f6ca91ce8bbfe56f64651523802410b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! ref: composer/src/Composer/Command/GlobalCommand.php

use std::path::Path;

use anyhow::Result;
use shirabe_external_packages::composer::pcre::preg::Preg;
use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput;
use shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions;
use shirabe_external_packages::symfony::console::input::input_interface::InputInterface;
use shirabe_external_packages::symfony::console::input::string_input::StringInput;
use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface;
use shirabe_php_shim::{chdir, LogicException, RuntimeException};

use crate::command::base_command::BaseCommand;
use crate::console::input::input_argument::InputArgument;
use crate::factory::Factory;
use crate::util::filesystem::Filesystem;
use crate::util::platform::Platform;

#[derive(Debug)]
pub struct GlobalCommand {
    inner: BaseCommand,
}

impl GlobalCommand {
    pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) {
        let application = self.inner.get_application();
        if input.must_suggest_argument_values_for("command-name") {
            let names: Vec<String> = application.all()
                .into_iter()
                .filter(|cmd| !cmd.is_hidden())
                .filter_map(|cmd| cmd.get_name().map(|n| n.to_string()))
                .collect();
            suggestions.suggest_values(names);
            return;
        }

        let command_name = input.get_argument("command-name").as_string().unwrap_or("").to_string();
        if application.has(&command_name) {
            let sub_input = self.prepare_subcommand_input(input.as_input_interface(), true);
            let sub_input = CompletionInput::from_string(&sub_input.to_string(), 2);
            let command = application.find(&command_name);
            command.merge_application_definition();
            sub_input.bind(command.get_definition());
            command.complete(&sub_input, suggestions);
        }
    }

    pub fn configure(&mut self) {
        self.inner
            .set_name("global")
            .set_description("Allows running commands in the global composer dir ($COMPOSER_HOME)")
            .set_definition(vec![
                InputArgument::new("command-name", Some(InputArgument::REQUIRED), "", None, vec![]),
                InputArgument::new("args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, vec![]),
            ])
            .set_help(
                "Use this command as a wrapper to run other Composer commands\n\
                within the global context of COMPOSER_HOME.\n\n\
                You can use this to install CLI utilities globally, all you need\n\
                is to add the COMPOSER_HOME/vendor/bin dir to your PATH env var.\n\n\
                COMPOSER_HOME is c:\\Users\\<user>\\AppData\\Roaming\\Composer on Windows\n\
                and /home/<user>/.composer on unix systems.\n\n\
                If your system uses freedesktop.org standards, then it will first check\n\
                XDG_CONFIG_HOME or default to /home/<user>/.config/composer\n\n\
                Note: This path may vary depending on customizations to bin-dir in\n\
                composer.json or the environmental variable COMPOSER_BIN_DIR.\n\n\
                Read more at https://getcomposer.org/doc/03-cli.md#global"
            );
    }

    pub fn run(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> {
        let tokens = Preg::split(r"{\s+}", &input.to_string())?;
        let mut args: Vec<String> = vec![];
        for token in &tokens {
            if !token.is_empty() && !token.starts_with('-') {
                args.push(token.clone());
                if args.len() >= 2 {
                    break;
                }
            }
        }

        if args.len() < 2 {
            return self.inner.run(input, output);
        }

        let sub_input = self.prepare_subcommand_input(input, false)?;
        Ok(self.inner.get_application().run(&sub_input, output)?)
    }

    fn prepare_subcommand_input(&self, input: &dyn InputInterface, quiet: bool) -> Result<StringInput> {
        if Platform::get_env("COMPOSER").is_some() {
            Platform::clear_env("COMPOSER");
        }

        let config = Factory::create_config(None, None)?;
        let home = config.get("home").as_string().unwrap_or("").to_string();

        if !Path::new(&home).is_dir() {
            let fs = Filesystem::new();
            fs.ensure_directory_exists(&home)?;
            if !Path::new(&home).is_dir() {
                return Err(RuntimeException {
                    message: "Could not create home directory".to_string(),
                    code: 0,
                }.into());
            }
        }

        chdir(&home).map_err(|e| RuntimeException { message: format!("Could not switch to home directory \"{}\"", home), code: 0 })?;

        if !quiet {
            self.inner.get_io().write_error(&format!("<info>Changed current directory to {}</info>", home));
        }

        let new_input_str = Preg::replace(r"{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}", "", &input.to_string(), 1)?;
        self.inner.get_application().reset_composer();

        Ok(StringInput::new(new_input_str))
    }

    pub fn is_proxy_command(&self) -> bool {
        true
    }
}