diff --git a/composer.json b/composer.json
index 65f58d1..81a5d27 100644
--- a/composer.json
+++ b/composer.json
@@ -24,12 +24,17 @@
"symfony-cmf/routing": "1.1.*@alpha",
"easyrdf/easyrdf": "0.8.*",
"phpunit/phpunit": "3.7.*",
- "zendframework/zend-feed": "2.2.*"
+ "zendframework/zend-feed": "~2.2",
+ "symfony/console": "2.4.*",
+ "symfony/finder": "~2.4",
+ "mikey179/vfsStream": "~1.2"
},
+ "prefer-stable": true,
"autoload": {
"psr-4": {
"Drupal\\Core\\": "core/lib/Drupal/Core",
"Drupal\\Component\\": "core/lib/Drupal/Component",
+ "Drupal\\Console\\": "core/lib/Drupal/Console",
"Drupal\\Driver\\": "drivers/lib/Drupal/Driver"
},
"files": [
diff --git a/core/console b/core/console
new file mode 100755
index 0000000..9ee2d76
--- /dev/null
+++ b/core/console
@@ -0,0 +1,30 @@
+#!/usr/bin/env php
+consoleServiceYamlFiles();
+foreach ($service_files as $file) {
+ $loader->load($file->getPathname());
+}
+
+$pass = new ConsoleCompilerPass();
+$container->addCompilerPass($pass);
+$container->compile();
+
+$app = $container->get('drupal_console_app');
+
+$app->run();
diff --git a/core/core.console.services.yml b/core/core.console.services.yml
new file mode 100644
index 0000000..d379f47
--- /dev/null
+++ b/core/core.console.services.yml
@@ -0,0 +1,15 @@
+parameters:
+ # Make the class a property so we can change it for testing.
+ drupal_console_app.class: Drupal\Console\DrupalConsoleApp
+
+services:
+ drupal_console_app:
+ class: "%drupal_console_app.class%"
+ tags:
+ - { name: drupal_console }
+
+ drupal_console.drupal_cron:
+ class: Drupal\Console\Command\DrupalCronCommand
+ tags:
+ - { name: drupal_console.command }
+ - { name: drupal_console }
diff --git a/core/lib/Drupal/Console/Command/CommandBase.php b/core/lib/Drupal/Console/Command/CommandBase.php
new file mode 100644
index 0000000..9adf5c4
--- /dev/null
+++ b/core/lib/Drupal/Console/Command/CommandBase.php
@@ -0,0 +1,19 @@
+addOption(
+ 'environment', 'e', InputOption::VALUE_REQUIRED, 'Kernel environment.', 'prod'
+ );
+ parent::configure();
+ }
+
+ /**
+ * Bootstrap Drupal enough so that we can run commands which require it.
+ *
+ * @param InputInterface $input
+ * Command input object.
+ * @param OutputInterface $output
+ * Command output object
+ *
+ * @returns DrupalKernel|FALSE
+ * Booted kernel, or FALSE.
+ */
+ protected function bootstrap(InputInterface $input, OutputInterface $output, Request $request = \NULL) {
+ // This defaults to 'prod.'
+ $env = $input->getOption('environment');
+ try {
+ // This boot strategy ripped from drupal_handle_request().
+ require_once __DIR__ . '/../../../../includes/bootstrap.inc';
+ drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+ $kernel = new DrupalKernel($env, \drupal_classloader(), TRUE);
+ $kernel->boot();
+
+ if (!$request) {
+ // Create a meaningful request object. We shouldn't really need this but
+ // Drupal complains if it's not present.
+ $request = Request::createFromGlobals();
+ }
+ $container = $kernel->getContainer();
+ $container->set('request', $request);
+
+ // Don't ask me why we need this voodo. It's in drupal_handle_request().
+ $container->get('request_stack')->push($request);
+
+ drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+ return $kernel;
+ }
+ catch (\Exception $e) {
+ $formatter = $this->getHelperSet()->get('formatter');
+ $error_messages = array(
+ 'Insufficient Drupal to proceed.',
+ 'This command requires a bootable Drupal installation.',
+ );
+ $formatted_block = $formatter->formatBlock($error_messages, 'error', TRUE);
+ $output->writeln($formatted_block);
+ }
+ return FALSE;
+ }
+
+}
diff --git a/core/lib/Drupal/Console/Command/DrupalCronCommand.php b/core/lib/Drupal/Console/Command/DrupalCronCommand.php
new file mode 100644
index 0000000..58470e6
--- /dev/null
+++ b/core/lib/Drupal/Console/Command/DrupalCronCommand.php
@@ -0,0 +1,40 @@
+setName('drupal:cron')
+ ->setDescription('Perform a cron run.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $kernel = $this->bootstrap($input, $output);
+ if ($kernel) {
+ $output->writeln('Running cron...');
+ $cron = $kernel->getContainer()->get('cron');
+ $cron->run();
+ $output->writeln('Done.');
+ return;
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Console/ConsoleCompilerPass.php b/core/lib/Drupal/Console/ConsoleCompilerPass.php
new file mode 100644
index 0000000..dccbc80
--- /dev/null
+++ b/core/lib/Drupal/Console/ConsoleCompilerPass.php
@@ -0,0 +1,56 @@
+findTaggedServiceIds('drupal_console.command');
+ // Remove all services which aren't console-related. We don't want to
+ // instantiate any classes which have any dependencies other than those for
+ // console.
+/* $services = $container->getServiceIds();
+ foreach ($services as $service) {
+ if (!in_array($service, array('service_container', 'drupal_console_app'))) {
+ if (!in_array($service, $tagged_services)) {
+ $container->removeDefinition($service);
+ }
+ }
+ }
+*/
+ // Tell the container to add these services to the console app.
+ $definition = $container->getDefinition('drupal_console_app');
+ foreach ($tagged_services as $id => $attributes) {
+ $definition->addMethodCall(
+ 'add', array(new Reference($id))
+ );
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Console/Discovery/DrupalConsoleServicesFinder.php b/core/lib/Drupal/Console/Discovery/DrupalConsoleServicesFinder.php
new file mode 100644
index 0000000..189d1cf
--- /dev/null
+++ b/core/lib/Drupal/Console/Discovery/DrupalConsoleServicesFinder.php
@@ -0,0 +1,29 @@
+inDrupalCore()
+ ->inContrib()
+ ->excludeVendor()
+ // Only files, no directories.
+ ->files()
+ // Don't try to look in forbidden places.
+ ->ignoreUnreadableDirs()
+ // Ignore .git directories.
+ ->ignoreVCS(TRUE);
+
+ // We want console service YAML files.
+ $this->name('*.console.services.yml');
+
+ return $this->getIterator();
+ }
+
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Console/DrupalConsoleApp.php b/core/lib/Drupal/Console/DrupalConsoleApp.php
new file mode 100644
index 0000000..2f2f301
--- /dev/null
+++ b/core/lib/Drupal/Console/DrupalConsoleApp.php
@@ -0,0 +1,73 @@
+inDrupalCore()
+ ->inContrib()
+ ->excludeVendor()
+ // Only files, no directories.
+ ->files()
+ // Don't try to look in forbidden places.
+ ->ignoreUnreadableDirs()
+ // Ignore .git directories.
+ ->ignoreVCS(TRUE);
+
+ // We want console service YAML files.
+ $finder->name('*.console.services.yml');
+
+ // We keep a class file resolver around so we can use it in both discovery
+ // and loading, since it stores what it has looked up.
+ $resolver = new FileClassResolver();
+
+ // Use IsSubclassFilterIterator to find classes which are subclasses of
+ // CommandBase.
+ $filter_iterator = new IsSubclassFilterIterator(
+ $finder->getIterator(),
+ 'Drupal\\Console\\Command\\CommandBase',
+ $resolver
+ );
+
+ // Add the commands we found to the app.
+ foreach ($filter_iterator as $file) {
+ $class = $resolver->classInFile($file->getPathName());
+ $this->add(new $class());
+ }
+
+ return $this;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Discovery/DrupalFinder.php b/core/lib/Drupal/Core/Discovery/DrupalFinder.php
new file mode 100644
index 0000000..016b0e5
--- /dev/null
+++ b/core/lib/Drupal/Core/Discovery/DrupalFinder.php
@@ -0,0 +1,96 @@
+drupalRoot = $drupal_root;
+ }
+
+ /**
+ * Path to the root of the Drupal installation.
+ *
+ * @return string
+ * Path to the root of the Drupal installation.
+ */
+ public function drupalRoot() {
+ return $this->drupalRoot;
+ }
+
+ /**
+ * Path to the Drupal installation's core directory.
+ *
+ * @return string
+ * Path to the Drupal installation's core directory.
+ */
+ public function drupalCore() {
+ return $this->drupalRoot . '/core';
+ }
+
+ /**
+ * Set iterator to search the Drupal root directory.
+ *
+ * @return Drupal\Core\Discovery\DrupalFinder
+ * Fluent interface.
+ */
+ public function inDrupalRoot() {
+ return $this->in($this->drupalRoot);
+ }
+
+ /**
+ * Set iterator to search the Drupal core directory.
+ *
+ * @return Drupal\Core\Discovery\DrupalFinder
+ * Fluent interface.
+ */
+ public function inDrupalCore() {
+ return $this->in($this->drupalCore());
+ }
+
+ /**
+ * Set iterator to search within Drupal's contrib directories.
+ *
+ * @return Drupal\Core\Discovery\DrupalFinder
+ * Fluent interface.
+ */
+ public function inContrib() {
+ $this->in($this->drupalRoot . '/modules');
+ return $this->in($this->drupalRoot . '/sites');
+ }
+
+ /**
+ * Set iterator to exclude Composer's vendor directory.
+ *
+ * @return Drupal\Core\Discovery\DrupalFinder
+ * Fluent interface.
+ */
+ public function excludeVendor() {
+ return $this->exclude($this->drupalCore() . '/vendor');
+ }
+
+}