diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php new file mode 100644 index 000000000..114954606 --- /dev/null +++ b/src/Composer/Command/ConfigCommand.php @@ -0,0 +1,291 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use JsonSchema\Validator; +use Composer\Config; +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; + +class ConfigCommand extends Command +{ + /** + * @var Composer\Json\JsonFile + */ + protected $configFile; + + /** + * {@inheritDoc} + */ + protected function configure() + { + $this + ->setName('config') + ->setDescription('Set config options') + ->setDefinition(array( + new InputOption('global', 'g', InputOption::VALUE_NONE, 'Set this as a global config settings.'), + new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), + new InputArgument('setting-key', null, 'Setting key'), + new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), + )) + ->setHelp(<<USAGE: + +To edit the global config.json file: + + php composer.phar --global + +To add a repository: + + php composer.phar repositories.foo vcs http://bar.com + +You can add a repository to the global config.json file by passing in the +--global option. + +If you want to launch your editor with the composer.json file you must have "EDITOR" set. + + php composer.phar --edit + +To get a list of configuration values in the file, pass the --list option. + + php composer.phar --list + +You can always pass more than one option. As an example, if you want to edit the +global config.json file. + + php composer.phar --edit --global + +LIMITATIONS + +The command only supports repositories and process-timeout right now. + +EOT + ) + ; + } + + /** + * {@inheritDoc} + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + if ($input->getOption('global') && $input->getOption('file')) { + throw new \RuntimeException('Cannot use both, you must pick to edit either the global config or path to composer.json'); + } + + // Get the local composer.json, global config.json, or if the user + // passed in a file to use + $this->configFile = $input->getOption('global') + ? (Factory::createConfig()->get('home') . '/config.json') + : (null !== $input->getOption('file') + ? $input->getOption('file') + : 'composer.json'); + + $this->configFile = new JsonFile($this->configFile); + if (!$this->configFile->exists()) { + touch($this->configFile->getPath()); + // If you read an empty file, Composer throws an error + // Toss some of the defaults in there + $defaults = Config::$defaultConfig; + $defaults['repositories'] = Config::$defaultRepositories; + $this->configFile->write($defaults); + } + } + + /** + * {@inheritDoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // Open file in editor + if ($input->getOption('editor')) { + // @todo Find a way to use another editor + $editor = system("bash -cl 'echo \$EDITOR'"); + system($editor . ' ' . $this->configFile->getPath() . ' > `tty`'); + return 0; + } + + // List the configuration of the file settings + if ($input->getOption('list')) { + $this->displayFileContents($this->configFile->read(), $output); + return 0; + } + + // If the user enters in a config variable, parse it and save to file + if ($input->getArgument('setting-key')) { + if (null === $input->getArgument('setting-value')) { + throw new \RuntimeException('You must include a setting value.'); + } + /** + * The user needs the ability to add a repository with one command. + * For example "config -g repository.foo 'vcs http://example.com' + */ + $configSettings = $this->configFile->read(); // what is current in the config + $settings = array(); // This will what will be merged into the above + $values = $input->getArgument('setting-value'); // what the user is trying to add/change + + // Checking for each known config value is going to make this method very large + // what is a better way to do this? + + // repositories.foo + if (preg_match('/^repositories\.(.+)/', $input->getArgument('setting-key'), $matches)) { + if (2 !== count($values)) { + throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs http://bar.com'); + } + $setting = $this->parseSetting($input->getArgument('setting-key'), array( + 'type' => $values[0], + 'url' => $values[1], + )); + + // Could there be a better way to do this? + $settings = array_merge_recursive($configSettings, $setting); + $this->validateSchema($settings); + } + // process-timeout + elseif (preg_match('/^process-timeout/', $input->getArgument('setting-key'))) { + if (1 !== count($values)) { + throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); + } + + if (!is_numeric($values[0])) { + throw new \RuntimeException(sprintf('"%s" is not a number.', $values[0])); + } + + $setting = $this->parseSetting('config.'.$input->getArgument('setting-key'), (integer) $values[0]); + $settings = array_merge($configSettings, $setting); + $this->validateSchema($settings); + } + + // Make sure we have something to write to disk + if (!count($settings)) { + $output->writeln('Trying to update a setting that is supported with this command.'); + return 0; + } + + // Make confirmation + if ($input->isInteractive()) { + $dialog = $this->getHelperSet()->get('dialog'); + $output->writeln(JsonFile::encode($settings)); + if (!$dialog->askConfirmation($output, $dialog->getQuestion('Do you confirm?', 'yes', '?'), true)) { + $output->writeln('Command Aborted by User'); + return 1; + } + } + + $this->configFile->write($settings); + } + } + + /** + * {@inheritDoc} + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Display the contents of the file in a pretty formatted way + * + * @param array $contents + * @param OutputInterface $output + * @param integer $depth + * @param string|null $k + */ + protected function displayFileContents(array $contents, OutputInterface $output, &$depth = 0, $k = null) + { + // @todo Look into a way to refactor this code, as it is right now, I + // don't like it, also the name of the function could be better + foreach ($contents as $key => $value) { + if (is_array($value)) { + $depth++; + $k .= $key . '.'; + $this->displayFileContents($value, $output, $depth, $k); + if (substr_count($k,'.') > 1) { + $k = str_split($k,strrpos($k,'.',-2)); + $k = $k[0] . '.'; + } else { $k = null; } + $depth--; + continue; + } + $output->writeln('[' . $k . $key . '] ' . $value . ''); + } + } + + /** + * This function will take a setting key (a.b.c) and return an + * array that matches this + * + * @param string $key + * @param string $value + * @return array + */ + protected function parseSetting($key, $value) + { + $parts = array_reverse(explode('.', $key)); + $tmp = array(); + for($i=0;$iproperties->name, + $schemaData->properties->description + ); + + $validator = new Validator(); + $validator->check(json_decode(json_encode($data)), $schemaData); + + if (!$validator->isValid()) { + $errors = array(); + foreach ((array) $validator->getErrors() as $error) { + $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message']; + } + throw new JsonValidationException('"'.$this->configFile->getPath().'" does not match the expected JSON schema'."\n". implode("\n",$errors)); + } + + return true; + } +} + + diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index a63af7e51..856ea5583 100755 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -155,6 +155,7 @@ class Application extends BaseApplication { $commands = parent::getDefaultCommands(); $commands[] = new Command\AboutCommand(); + $commands[] = new Command\ConfigCommand(); $commands[] = new Command\DependsCommand(); $commands[] = new Command\InitCommand(); $commands[] = new Command\InstallCommand();