public function getResultIterator($path) { if ((Environment::isWindows() || Environment::isLinuxWithWine()) !== true) { $this->log(['FxCop requires Windows or Wine to execute', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } $compatibleVersions = ['Microsoft Fxcop 10.0', 'Microsoft Fxcop 1.36', 'Microsoft Fxcop 1.35']; $pluginDir = (new \Yasca\Core\IteratorBuilder())->from([__DIR__])->concat((new \Yasca\Core\IteratorBuilder())->from($compatibleVersions)->select(static function ($versionDir) { return __DIR__ . '/' . $versionDir; }))->concat((new \Yasca\Core\IteratorBuilder())->from(['ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432'])->where(static function ($specialDir) { return isset($_ENV[$specialDir]); })->select(static function ($specialDir) { return $_ENV[$specialDir]; })->selectMany(static function ($dir) use($compatibleVersions) { return (new \Yasca\Core\IteratorBuilder())->from($compatibleVersions)->select(static function ($versionDir) use($dir) { return "{$dir}/{$versionDir}"; }); }))->where(static function ($dir) { return (new \SplFileInfo("{$dir}/FxCopCmd.exe"))->isFile(); })->firstOrNull(); if ($pluginDir === null) { $this->log(['FxCop cannot be found. To enable the FxCop plugin, ' . 'install it from Microsoft in the default location ' . ' or copy FxCop to the ./Yasca/Plugins/FxCop directory.', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } try { $process = new Process((Environment::isLinuxWithWine() === true ? 'wine ' : '') . '"' . $pluginDir . '/FxCopCmd.exe" ' . '/ignoreinvalidtargets ' . '/searchgac ' . '/rule:"' . $pluginDir . '/Rules/SecurityRules.dll" ' . '/consolexsl:"' . __DIR__ . '/yasca.xsl" ' . '/quiet ' . (new \Yasca\Core\IteratorBuilder())->from(new \RecursiveDirectoryIterator($path))->whereRegex('`(?i)\\.(' . (new \Yasca\Core\IteratorBuilder())->from($this->getSupportedFileTypes())->select(static function ($element) { return \preg_quote($element, '`'); })->join('|') . ')$`u')->select(static function ($current) { return " /file:\"{$current}\""; })->join('')); } catch (ProcessStartException $e) { $this->log(['FxCop failed to start', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } $this->log(['FxCop launched', \Yasca\Logs\Level::INFO]); return $process->continueWith(function ($async) { list($stdout, $stderr) = $async->result(); $this->log(['FxCop completed', \Yasca\Logs\Level::INFO]); $dom = new \DOMDocument(); try { $success = $dom->loadXML($stdout); } catch (\ErrorException $e) { $success = false; } if ($success !== true) { if ($stdout === '') { $this->log(['FxCop did not return any data', \Yasca\Logs\Level::ERROR]); } else { $this->log(['FxCop did not return valid XML', \Yasca\Logs\Level::ERROR]); $this->log(["FxCop returned {$stdout}", \Yasca\Logs\Level::ERROR]); } return Async::fromResult(new \EmptyIterator()); } return (new \Yasca\Core\IteratorBuilder())->from($dom->getElementsByTagName('result'))->select(static function ($result) { return (new \Yasca\Result())->setOptions(['pluginName' => 'FxCop', 'severity' => "{$result->getAttribute('severity')}", 'category' => "{$result->getAttribute('category')}", 'filename' => "{$result->getAttribute('filename')}", 'references' => ["{$result->getAttribute('reference')}" => 'MSDN'], 'message' => "{$result->getAttribute('message')}", 'description' => "{$result->getAttribute('description')}"]); })->toFunctionPipe()->pipe([Async::_class, 'fromResult']); }); }
public function getResultIterator($path) { if (Environment::hasAtLeastJavaVersion(5) !== true) { $this->log(['FindBugs requires JRE 1.5 or later.', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } try { $process = new Process('"' . __DIR__ . '/bin/findbugs' . (Environment::isWindows() ? '.bat' : '') . '"' . ' -home "' . __DIR__ . '" ' . ' -include "' . __DIR__ . '/filter.xml" ' . '-textui -xml:withMessages -xargs -quiet'); } catch (ProcessStartException $e) { $this->log(['FindBugs failed to start', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } $this->log(['FindBugs launched', \Yasca\Logs\Level::INFO]); (new \Yasca\Core\FunctionPipe())->wrap($path)->pipe([Operators::_class, '_new'], '\\RecursiveDirectoryIterator')->toIteratorBuilder()->where(function ($fileinfo) { return $this->supportsExtension($fileinfo->getExtension()); })->select(static function ($fileinfo, $filepath) { return "{$filepath}\n"; })->forAll([$process, 'writeToStdin']); $process->closeStdin(); return $process->continueWith(function ($async) use($path) { list($stdout, $stderr) = $async->result(); $this->log(['FindBugs completed', \Yasca\Logs\Level::INFO]); $dom = new \DOMDocument(); try { $success = $dom->loadXML($stdout); } catch (\ErrorException $e) { $success = false; } if ($success !== true) { if ($stdout === '') { $this->log(['FindBugs did not return any data', \Yasca\Logs\Level::ERROR]); } else { $this->log(['FindBugs did not return valid XML', \Yasca\Logs\Level::ERROR]); $this->log(["FindBugs returned {$stdout}", \Yasca\Logs\Level::ERROR]); } return Async::fromResult(new \EmptyIterator()); } $bugPatterns = (new \Yasca\Core\IteratorBuilder())->from($dom->getElementsByTagName('BugPattern'))->selectKeys(static function ($patternNode) { return ["{$pattern->getElementsByTagName('Details')->item(0)->nodeValue}", "{$pattern->getAttribute('type')}"]; })->toArray(true); return (new \Yasca\Core\IteratorBuilder())->from($dom->getElementsByTagName('BugInstance'))->select(static function ($bugInstance) use(&$bugPatterns, $path) { $type = $bugInstance->getAttribute('type'); $sourceLine = $bugInstance->getElementsByTagName('SourceLine')->item(0); $shortMessage = $bugInstance->getElementsByTagName('ShortMessage')->item(0)->nodeValue; return (new \Yasca\Result())->setOptions(['pluginName' => 'FindBugs', 'severity' => "{$bugInstance->getAttribute('priority')}", 'category' => (new \Yasca\Core\FunctionPipe())->wrap($bugInstance->getAttribute('category'))->pipeLast('\\str_replace', '_', ' ')->pipe('\\strtolower')->pipe('\\ucwords')->unwrap(), 'lineNumber' => "{$sourceLine->getAttribute('start')}", 'filename' => "{$path}/{$sourceLine->getAttribute('sourcepath')}", 'references' => ['http://findbugs.sourceforge.net/bugDescriptions.html#' . \urlencode($type) => 'FindBugs Bug Description'], 'message' => "{$shortMessage}", 'description' => <<<EOT {$shortMessage} {$bugPatterns[$type]} EOT ]); })->toFunctionPipe()->pipe([Async::_class, 'fromResult']); }); }
public function getResultIterator($path) { if (Environment::hasAtLeastJavaVersion(4) !== true) { $this->log(['PMD requires JRE 1.4 or later.', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } try { $process = new Process('java -cp "' . (new \Yasca\Core\FunctionPipe())->wrap(__DIR__)->pipe([Operators::_class, '_new'], '\\FilesystemIterator')->toIteratorBuilder()->select(static function ($u, $key) { return $key; })->whereRegex('`\\.jar$`ui')->join(PATH_SEPARATOR) . '" net.sourceforge.pmd.PMD "' . $path . '"' . ' xml' . ' "' . __DIR__ . '/yasca-rules.xml"'); // ' "' . __DIR__ . '/yasca-rules.xml"' //); } catch (ProcessStartException $e) { $this->log(['PMD failed to start', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } $this->log(['PMD launched', \Yasca\Logs\Level::INFO]); return $process->continueWith(function ($async) { list($stdout, $stderr) = $async->result(); $this->log(['PMD completed', \Yasca\Logs\Level::INFO]); //$this->log([$stdout, \Yasca\Logs\Level::ERROR]); $dom = new \DOMDocument(); try { $success = $dom->loadXML($stdout); } catch (\ErrorException $e) { $success = false; } if ($success !== true) { if ($stdout === '') { $this->log(['PMD did not return any data', \Yasca\Logs\Level::ERROR]); $this->log([$stderr, \Yasca\Logs\Level::ERROR]); } else { $this->log(['PMD did not return valid XML', \Yasca\Logs\Level::ERROR]); $this->log(["PMD returned {$stdout}", \Yasca\Logs\Level::ERROR]); } return Async::fromResult(new \EmptyIterator()); } return (new \Yasca\Core\IteratorBuilder())->from($dom->getElementsByTagName('file'))->selectMany(static function ($fileNode) { return (new \Yasca\Core\IteratorBuilder())->from($fileNode->getElementsByTagName('violation'))->select(static function ($violationNode) use($fileNode) { return (new \Yasca\Result())->setOptions(['pluginName' => 'PMD', 'filename' => "{$fileNode->getAttribute('name')}", 'lineNumber' => "{$violationNode->getAttribute('beginline')}", 'category' => "{$violationNode->getAttribute('rule')}", 'severity' => "{$violationNode->getAttribute('priority')}", 'description' => "", 'message' => "", 'references' => ["{$violationNode->getAttribute('externalInfoUrl')}" => 'PMD Reference']]); }); })->toFunctionPipe()->pipe([Async::_class, 'fromResult']); }); }
public function getResultIterator($path) { if (Environment::isWindows() !== true) { $this->log(['The copy of CppCheck included with Yasca requires Windows', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } try { $process = new Process('"' . __DIR__ . '/cppcheck" ' . '--quiet ' . '--enable=all ' . '--inline-suppr ' . '--xml ' . '"' . $path . '"'); } catch (ProcessStartException $e) { $this->log(['CppCheck failed to start', \Yasca\Logs\Level::ERROR]); return new \EmptyIterator(); } $this->log(['CppCheck launched', \Yasca\Logs\Level::INFO]); return $process->continueWith(function ($async) { list($stdout, $stderr) = $async->result(); $this->log(['CppCheck completed', \Yasca\Logs\Level::INFO]); $regex = <<<'EOT' `No C or C\+\+ source files found\.`u EOT; if (\preg_match($regex, $stderr)) { $this->log(['CppCheck did not find any C or C++ source files', \Yasca\Logs\Level::ERROR]); return Async::fromResult(new \EmptyIterator()); } $dom = new \DOMDocument(); try { $success = $dom->loadXML($stderr); } catch (\ErrorException $e) { $success = false; } if ($success !== true) { if ($stderr === '') { $this->log(['CppCheck did not return any data', \Yasca\Logs\Level::ERROR]); } else { $this->log(['CppCheck did not return valid XML', \Yasca\Logs\Level::ERROR]); $this->log(["CppCheck returned {$stderr}", \Yasca\Logs\Level::ERROR]); } return Async::fromResult(new \EmptyIterator()); } return (new \Yasca\Core\IteratorBuilder())->from($dom->getElementsByTagName('error'))->select(static function ($errorNode) { return (new \Yasca\Result())->setOptions(['pluginName' => 'CppCheck', 'category' => "{$errorNode->getAttribute('id')}", 'lineNumber' => "{$errorNode->getAttribute('line')}", 'filename' => "{$errorNode->getAttribute('file')}", 'message' => "{$errorNode->getAttribute('msg')}", 'description' => "{$errorNode->getAttribute('msg')}", 'references' => ['http://sourceforge.net/projects/cppcheck/' => 'CppCheck Home Page'], 'severity' => (new \Yasca\Core\FunctionPipe())->wrap($errorNode->getAttribute('severity'))->pipe(static function ($cppcheckSeverity) { //http://cppcheck.sourceforge.net/devinfo/doxyoutput/classSeverity.html if ($cppcheckSeverity === 'error') { return 2; } elseif ($cppcheckSeverity === 'warning') { return 3; } elseif ($cppcheckSeverity === 'portability' || $cppcheckSeverity === 'performance' || $cppcheckSeverity === 'style' || $cppcheckSeverity === 'portability') { return 4; } else { //$cppcheckSeverity === 'debug' //$cppcheckSeverity === 'information' //$cppcheckSeverity === 'none' return 5; } })->unwrap()]); })->where(static function ($result) { $category = $result->category; if ($category === 'toomanyconfigs' || $category === 'syntaxError' || $category === 'cppcheckError') { return false; } else { return true; } })->toFunctionPipe()->pipe([Async::_class, 'fromResult']); }); }
public function __construct($options){ list($subscribeIfCloseable, $closeSubscribedCloseables) = Operators::invoke(static function(){ $closeables = new \SplObjectStorage(); return [ static function($object) use ($closeables){ if ( (new \Yasca\Core\IteratorBuilder) ->from(Iterators::traitsOf($object)) ->contains('Yasca\Core\Closeable') ){ $closeables->attach($object); } }, static function() use ($closeables){ foreach($closeables as $closeable){ $closeable->close(); } $closeables->removeAllExcept(new \SplObjectStorage()); }, ]; }); list($fireLogEvent, $fireResultEvent) = Operators::invoke(function() use ($subscribeIfCloseable){ $newEvent = function($name) use ($subscribeIfCloseable){ $event = new SplSubjectAdapter(); $this->{"attach{$name}Observer"} = function(\SplObserver $observer) use ($event, $subscribeIfCloseable){ $event->attach($observer); $subscribeIfCloseable($observer); return $this; }; $this->{"detach{$name}Observer"} = function(\SplObserver $observer) use ($event, $subscribeIfCloseable){ $event->detach($observer); $subscribeIfCloseable($observer); return $this; }; return static function($value) use ($event){ $event->raise($value); }; }; return [ $newEvent('Log'), $newEvent('Result'), ]; }); $targetDirectory = (new \Yasca\Core\FunctionPipe) ->wrap($options) ->pipe([Iterators::_class,'elementAtOrNull'], 'targetDirectory') ->pipe('\realpath') ->unwrap(); $makeRelative = //Make filenames relative when publishing a result (new \Yasca\Core\FunctionPipe) ->wrap($targetDirectory) ->pipe('\preg_quote', '`') ->pipe(static function($dirLiteral) { return "`^$dirLiteral`ui"; }) ->pipe(static function($regex){ return Operators::curry('\preg_replace', $regex, ''); }) ->unwrap(); //Wrap Result event trigger to make changes to each Result $fireResultEvent = static function(Result $result) use ($fireResultEvent, $makeRelative){ //Make adjustments based on adjustments data (new \Yasca\Core\FunctionPipe) ->wrap(static::$adjustments) ->pipe([Iterators::_class, 'elementAtOrNull'], $result->pluginName) ->pipe([Iterators::_class, 'elementAtOrNull'], $result->category) ->pipe(static function($options) use ($result){ if ($options !== null){ $result->setOptions($options); } }); //Get unsafeSourceCode if needed, and then make the filename relative //to the scan directory if (isset($result->filename) === true && !Operators::isNullOrEmpty($result->filename)){ if(isset($result->lineNumber) === true && isset($result->unsafeSourceCode) !== true){ try { $result->unsafeSourceCode = (new \Yasca\Core\FunctionPipe) ->wrap($result->filename) ->pipe([Encoding::_class,'getFileContentsAsArray']) ->toIteratorBuilder() ->slice( \max($result->lineNumber - 10, 0), 20 ) ->toArray(true); } catch (\ErrorException $e){ $tail = 'No such file or directory'; if (\substr($e->getMessage(),0-strlen($tail)) === $tail){ //External tool generated a filename that's not present //FindBugs can often do this if the matching .java files are missing. } else { throw $e; } } } $result->setOptions([ 'filename' => "{$makeRelative($result->filename)}", ]); } $fireResultEvent($result); }; $createPlugins = Operators::curry( static function($ignoreRegex, $onlyRegex) use ($fireLogEvent){ $retval = (new \Yasca\Core\IteratorBuilder) ->from(Plugin::$installedPlugins) ->select(static function($plugins) use ($ignoreRegex, $onlyRegex, $fireLogEvent){ return (new \Yasca\Core\IteratorBuilder) ->from($plugins) ->whereRegex($ignoreRegex) ->whereRegex($onlyRegex) ->select(static function($pluginName) use ($fireLogEvent){ $p = new $pluginName($fireLogEvent); $fireLogEvent(["Plugin $pluginName Loaded", \Yasca\Logs\Level::DEBUG]); return $p; }) ->toObjectStorage(); }) ->where([Iterators::_class,'any']) ->toArray(true); $fireLogEvent(['Selected Plugins Loaded', \Yasca\Logs\Level::DEBUG]); return $retval; }, (new \Yasca\Core\FunctionPipe) ->wrap($options) ->pipe([Iterators::_class, 'elementAtOrNull'], 'pluginsIgnore') ->toIteratorBuilder() ->select(static function($literal){return \preg_quote($literal, '`');}) ->toFunctionPipe() ->pipe([Iterators::_class, 'join'], '|') ->pipe(static function($string){ if (Operators::isNullOrEmpty($string) === true){ return null; } else { return "`^(?!.*($string).*$)`u"; } }) ->unwrap(), (new \Yasca\Core\FunctionPipe) ->wrap($options) ->pipe([Iterators::_class, 'elementAtOrNull'], 'pluginsOnly') ->toIteratorBuilder() ->select(static function($literal){return \preg_quote($literal, '`');}) ->toFunctionPipe() ->pipe([Iterators::_class, 'join'], '|') ->pipe(static function($string){ if (Operators::isNullOrEmpty($string) === true){ return null; } else { return "`($string)`u"; } }) ->unwrap() ); $createTargetIterator = Operators::curry( static function($extensionRegex, $extensionsIgnoreRegex, $extensionsOnlyRegex, $pluginArray) use ($targetDirectory){ //Only select files that plugins ask for return (new \Yasca\Core\IteratorBuilder) ->from(new \RecursiveDirectoryIterator( $targetDirectory, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::UNIX_PATHS )) ->whereRegex($extensionRegex($pluginArray), \RegexIterator::MATCH, \RegexIterator::USE_KEY) ->whereRegex($extensionsIgnoreRegex, \RegexIterator::MATCH, \RegexIterator::USE_KEY) ->whereRegex($extensionsOnlyRegex, \RegexIterator::MATCH, \RegexIterator::USE_KEY) ; }, static function($pluginArray){ return (new \Yasca\Core\IteratorBuilder) ->from($pluginArray) ->selectMany(static function($plugins){ return (new \Yasca\Core\IteratorBuilder) ->from($plugins); }) ->selectMany(static function($plugin){ return (new \Yasca\Core\IteratorBuilder) ->from($plugin->getSupportedFileTypes()); }) ->unique() ->select(static function($ext){ return \preg_quote($ext, '`'); }) ->toFunctionPipe() ->pipe([Iterators::_class, 'join'], '|') ->pipe(static function($string){ if (Operators::isNullOrEmpty($string) === true){ return null; } else { return "`\.($string)$`ui"; } }) ->unwrap(); }, (new \Yasca\Core\FunctionPipe) ->wrap($options) ->pipe([Iterators::_class, 'elementAtOrNull'], 'extensionsIgnore') ->toIteratorBuilder() ->select(static function($ext){return '.' . \trim($ext, '.');}) ->select(static function($literal){return \preg_quote($literal, '`');}) ->toFunctionPipe() ->pipe([Iterators::_class, 'join'], '|') ->pipe(static function($string){ if (Operators::isNullOrEmpty($string) === true){ return null; } else { return "`(?<!$string)$`ui"; } }) ->unwrap(), (new \Yasca\Core\FunctionPipe) ->wrap($options) ->pipe([Iterators::_class, 'elementAtOrNull'], 'extensionsOnly') ->toIteratorBuilder() ->select(static function($ext){return '.' . \trim($ext, '.');}) ->select(static function($literal){return \preg_quote($literal, '`');}) ->toFunctionPipe() ->pipe([Iterators::_class, 'join'], '|') ->pipe(static function($string){ if (Operators::isNullOrEmpty($string) === true){ return null; } else { return "`($string)$`ui"; } }) ->unwrap() ); $debug = (new \Yasca\Core\FunctionPipe) ->wrap($options) ->pipe([Iterators::_class,'elementAtOrNull'], 'debug') ->pipe([Operators::_class,'equals'], true) ->unwrap(); $processResults = static function($results) use ($fireResultEvent, &$processResults){ if ($results instanceof Result){ $fireResultEvent($results); return new \EmptyIterator(); } elseif ($results instanceof Async){ if ($results->isDone() === true){ return $processResults($results->result()); } else { return Iterators::ensureIsIterator([$results]); } } elseif ($results instanceof Wrapper){ return $processResults($results->unwrap()); } else { return (new \Yasca\Core\IteratorBuilder) ->from($results) ->selectMany($processResults); } }; $this->executeAsync = static function() use ( $fireLogEvent, $processResults, $closeSubscribedCloseables, $debug, $makeRelative, $createPlugins, $targetDirectory, $createTargetIterator ){ try { $fireLogEvent(['Yasca ' . Scanner::VERSION . ' - http://www.yasca.org/ - Michael V. Scovetta', \Yasca\Logs\Level::INFO]); $fireLogEvent(["Scanning $targetDirectory", \Yasca\Logs\Level::INFO]); $plugins = $createPlugins(); $multicasts = Iterators::elementAtOrNull($plugins, __NAMESPACE__ . '\MulticastPlugin'); $lastStatusReportedTime = \time(); $filesProcessed = 0; $awaits = []; foreach($createTargetIterator($plugins) as $filePath => $targetFileInfo){ $fireLogEvent(["Checking file {$makeRelative($filePath)}", \Yasca\Logs\Level::DEBUG]); $n = \time(); if ($n - $lastStatusReportedTime > self::SECONDS_PER_NOTIFY){ $fireLogEvent(["$filesProcessed files scanned", \Yasca\Logs\Level::INFO]); $lastStatusReportedTime = $n; } $ext = $targetFileInfo->getExtension(); $getFileContents = (new \Yasca\Core\FunctionPipe) ->wrap($filePath) ->pipeLast([Operators::_class,'curry'], [Encoding::_class, 'getFileContentsAsArray']) ->pipe([Operators::_class,'lazy']) ->unwrap(); $awaits = (new \Yasca\Core\IteratorBuilder) ->from($awaits) ->concat( //Multicast plugin results (new \Yasca\Core\IteratorBuilder) ->from($multicasts) //Make a copy to allow removing elements iterated over ->toFunctionPipe() ->pipe([Iterators::_class,'toList']) ->toIteratorBuilder() ->where(static function($plugin) use ($ext){ return $plugin->supportsExtension($ext); }) ->select(static function($plugin) use ($multicasts, $targetDirectory){ $multicasts->detach($plugin); return $plugin->getResultIterator($targetDirectory); }), //Single file path plugin results (new \Yasca\Core\FunctionPipe) ->wrap($plugins) ->pipe([Iterators::_class, 'elementAtOrNull'], __NAMESPACE__ . '\SingleFilePathPlugin') ->toIteratorBuilder() ->where(static function($plugin) use ($ext){ return $plugin->supportsExtension($ext); }) ->select(static function($plugin) use ($filePath){ return $plugin->getResultIterator($filePath); }), //Single file contents plugin results (new \Yasca\Core\FunctionPipe) ->wrap($plugins) ->pipe([Iterators::_class, 'elementAtOrNull'], __NAMESPACE__ . '\SingleFileContentsPlugin') ->toIteratorBuilder() ->where(static function($plugin) use ($ext){ return $plugin->supportsExtension($ext); }) ->select(static function($plugin) use ($getFileContents, $filePath){ return $plugin->getResultIterator($getFileContents(), $filePath); }) ) ->selectMany($processResults) ->toList(); (new \Yasca\Core\FunctionPipe) ->wrap($plugins) ->pipe([Iterators::_class, 'elementAtOrNull'], __NAMESPACE__ . '\AggregateFileContentsPlugin') ->toIteratorBuilder() ->where(static function($plugin) use ($ext){ return $plugin->supportsExtension($ext); }) ->forAll(static function($plugin) use ($getFileContents, $filePath){ $plugin->apply($getFileContents(), $filePath); }); Async::tickAll(); $filesProcessed += 1; } $fireLogEvent(['Finished with files. Gathering results from Aggregate plugins', \Yasca\Logs\Level::DEBUG]); $awaits = (new \Yasca\Core\FunctionPipe) ->wrap($plugins) ->pipe([Iterators::_class, 'elementAtOrNull'], __NAMESPACE__ . '\AggregateFileContentsPlugin') ->toIteratorBuilder() ->select(static function($plugin){return $plugin->getResultIterator();}) ->concat($awaits) ->selectMany($processResults) ->toList(); if (Iterators::any($awaits) === true){ $fireLogEvent(['Waiting on external plugins', \Yasca\Logs\Level::INFO]); return (new Async( static function() use ( &$awaits, $processResults, $fireLogEvent ){ $awaits = (new \Yasca\Core\IteratorBuilder) ->from($awaits) ->selectMany($processResults) ->toList(); return Iterators::any($awaits) === false; }, static function() use ($fireLogEvent) { $fireLogEvent(['Scan complete', \Yasca\Logs\Level::INFO]); return null; }, static function(\Exception $exception) use ($fireLogEvent, $debug){ $fireLogEvent(['Scan aborted', \Yasca\Logs\Level::ERROR]); if ($debug === true){ throw $exception; } else { $fireLogEvent([$exception->getMessage(), \Yasca\Logs\Level::ERROR]); return null; } } )) ->whenDone($closeSubscribedCloseables); } else { $fireLogEvent(['Scan complete', \Yasca\Logs\Level::INFO]); return Async::fromResult(null)->whenDone($closeSubscribedCloseables); } } catch (\Exception $exception){ $fireLogEvent(['Scan aborted', \Yasca\Logs\Level::ERROR]); if ($debug === true) { $closeSubscribedCloseables(); throw $exception; } else { $fireLogEvent([$exception->getMessage(), \Yasca\Logs\Level::ERROR]); return Async::fromResult(null)->whenDone($closeSubscribedCloseables); } } }; $this->execute = function(){ $f = $this->executeAsync; return $f()->result(); }; }