diff --git a/.gitignore b/.gitignore index aae4db8a3601..ff6640ed343f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ build/ composer.phar composer.lock -docs/json/**/*.json +docs/json/* vendor/ diff --git a/.travis.yml b/.travis.yml index 01f68113bc39..0c37a6d81b32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,31 @@ language: php - sudo: required dist: trusty matrix: - include: - - php: 5.5.38 - - php: 5.6.25 - - php: 7.0 - - php: 7.1 - - php: hhvm - group: edge - fast_finish: true + include: + - php: 5.5.38 + - php: 5.6.25 + - php: 7.0 + - php: 7.1 + - php: hhvm + group: edge + fast_finish: true before_script: - - pecl install grpc || echo 'Failed to install grpc' - - composer install + - pecl install grpc || echo 'Failed to install grpc' + - composer install script: - - ./dev/sh/tests - - vendor/bin/phpcs --standard=./phpcs-ruleset.xml - - ./dev/sh/build-docs + - ./dev/sh/tests + - vendor/bin/phpcs --standard=./phpcs-ruleset.xml + - ./dev/sh/build-docs after_success: - - bash <(curl -s https://codecov.io/bash) - - ./dev/sh/push-docs + - bash <(curl -s https://codecov.io/bash) + - ./dev/sh/push-docs + - ./dev/sh/trigger-split + - cat ./build/snippets-uncovered.json + +after_failure: + - echo "SNIPPET COVERAGE REPORT" && cat ./build/snippets-uncovered.json diff --git a/README.md b/README.md index 0c6a91082dfb..43ea2d961b1b 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ This client supports the following Google Cloud Platform services at a [Beta](#v * [Google Stackdriver Logging](#google-stackdriver-logging-beta) (Beta) * [Google Cloud Datastore](#google-cloud-datastore-beta) (Beta) * [Google Cloud Storage](#google-cloud-storage-beta) (Beta) +* [Google Cloud Vision](#google-cloud-vision-beta) (Beta) This client supports the following Google Cloud Platform services at an [Alpha](#versioning) quality level: * [Google Cloud Natural Language](#google-cloud-natural-language-alpha) (Alpha) * [Google Cloud Pub/Sub](#google-cloud-pubsub-alpha) (Alpha) * [Google Cloud Speech](#google-cloud-speech-alpha) (Alpha) * [Google Cloud Translation](#google-cloud-translation-alpha) (Alpha) -* [Google Cloud Vision](#google-cloud-vision-alpha) (Alpha) If you need support for other Google APIs, please check out the [Google APIs Client Library for PHP](https://github.com/google/google-api-php-client). @@ -61,6 +61,14 @@ foreach ($queryResults->rows() as $row) { } ``` +#### google/cloud-bigquery + +Google BigQuery can be installed separately by requiring the `google/cloud-bigquery` composer package: + +``` +$ require google/cloud-bigquery +``` + ## Google Stackdriver Logging (Beta) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/logging/loggingclient) @@ -93,6 +101,14 @@ foreach ($entries as $entry) { } ``` +#### google/cloud-logging + +Google Stackdriver Logging can be installed separately by requiring the `google/cloud-logging` composer package: + +``` +$ require google/cloud-logging +``` + ## Google Cloud Datastore (Beta) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/datastore/datastoreclient) @@ -124,6 +140,14 @@ $key = $datastore->key('Person', '12345328897844'); $entity = $datastore->lookup($key); ``` +#### google/cloud-datastore + +Google Cloud Datastore can be installed separately by requiring the `google/cloud-datastore` composer package: + +``` +$ require google/cloud-datastore +``` + ## Google Cloud Storage (Beta) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/storage/storageclient) @@ -152,6 +176,69 @@ $object = $bucket->object('file_backup.txt'); $object->downloadToFile('/data/file_backup.txt'); ``` +#### Stream Wrapper + +```php +require 'vendor/autoload.php'; + +use Google\Cloud\Storage\StorageClient; + +$storage = new StorageClient([ + 'projectId' => 'my_project' +]); +$storage->registerStreamWrapper(); + +$contents = file_get_contents('gs://my_bucket/file_backup.txt'); +``` + +#### google/cloud-storage + +Google Cloud Storage can be installed separately by requiring the `google/cloud-storage` composer package: + +``` +$ require google/cloud-storage +``` + +## Google Cloud Vision (Beta) + +- [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/vision/visionclient) +- [Official Documentation](https://cloud.google.com/vision/docs) + +#### Preview + +```php +require 'vendor/autoload.php'; + +use Google\Cloud\Vision\VisionClient; + +$vision = new VisionClient([ + 'projectId' => 'my_project' +]); + +// Annotate an image, detecting faces. +$image = $vision->image( + fopen('/data/family_photo.jpg', 'r'), + ['faces'] +); + +$annotation = $vision->annotate($image); + +// Determine if the detected faces have headwear. +foreach ($annotation->faces() as $key => $face) { + if ($face->hasHeadwear()) { + echo "Face $key has headwear.\n"; + } +} +``` + +#### google/cloud-vision + +Google Cloud Vision can be installed separately by requiring the `google/cloud-vision` composer package: + +``` +$ require google/cloud-vision +``` + ## Google Cloud Translation (Alpha) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/translate/translateclient) @@ -198,6 +285,14 @@ foreach ($languages as $language) { } ``` +#### google/cloud-translate + +Google Cloud Translation can be installed separately by requiring the `google/cloud-translate` composer package: + +``` +$ require google/cloud-translate +``` + ## Google Cloud Natural Language (Alpha) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/naturallanguage/naturallanguageclient) @@ -237,6 +332,14 @@ foreach ($tokens as $token) { } ``` +#### google/cloud-natural-language + +Google Cloud Natural Language can be installed separately by requiring the `google/cloud-natural-language` composer package: + +``` +$ require google/cloud-natural-language +``` + ## Google Cloud Pub/Sub (Alpha) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/pubsub/pubsubclient) @@ -276,6 +379,14 @@ foreach ($messages as $message) { } ``` +#### google/cloud-pubsub + +Google Cloud Pub/Sub can be installed separately by requiring the `google/cloud-pubsub` composer package: + +``` +$ require google/cloud-pubsub +``` + ## Google Cloud Speech (Alpha) - [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/speech/speechclient) @@ -303,36 +414,12 @@ foreach ($results as $result) { } ``` -## Google Cloud Vision (Alpha) +#### google/cloud-speech -- [API Documentation](http://googlecloudplatform.github.io/google-cloud-php/#/docs/latest/vision/visionclient) -- [Official Documentation](https://cloud.google.com/vision/docs) - -#### Preview - -```php -require 'vendor/autoload.php'; - -use Google\Cloud\Vision\VisionClient; +Google Cloud Speech can be installed separately by requiring the `google/cloud-speech` composer package: -$vision = new VisionClient([ - 'projectId' => 'my_project' -]); - -// Annotate an image, detecting faces. -$image = $vision->image( - fopen('/data/family_photo.jpg', 'r'), - ['faces'] -); - -$annotation = $vision->annotate($image); - -// Determine if the detected faces have headwear. -foreach ($annotation->faces() as $key => $face) { - if ($face->hasHeadwear()) { - echo "Face $key has headwear.\n"; - } -} +``` +$ require google/cloud-speech ``` ## Caching Access Tokens diff --git a/composer.json b/composer.json index 8e2b924f3f15..00dc2f0e8001 100644 --- a/composer.json +++ b/composer.json @@ -55,8 +55,8 @@ "james-heinrich/getid3": "^1.9", "erusev/parsedown": "^1.6", "vierbergenlars/php-semver": "^3.0", - "google/proto-client-php": "^0.7", - "google/gax": "^0.6" + "google/proto-client-php": "^0.9", + "google/gax": "^0.8" }, "suggest": { "google/gax": "Required to support gRPC", @@ -72,9 +72,18 @@ "psr-4": { "Google\\Cloud\\Dev\\": "dev/src", "Google\\Cloud\\Tests\\System\\": "tests/system" - } + }, + "files": ["dev/src/Functions.php"] }, "scripts": { "google-cloud": "dev/google-cloud" + }, + "extra": { + "component": { + "id": "google-cloud", + "target": "git@github.com:jdpedrie-gcp/google-cloud-php.git", + "path": "src", + "entry": "ServiceBuilder.php" + } } } diff --git a/dev/google-cloud b/dev/google-cloud index cd0636602a4f..e004b499d438 100755 --- a/dev/google-cloud +++ b/dev/google-cloud @@ -20,6 +20,7 @@ require __DIR__ . '/../vendor/autoload.php'; use Google\Cloud\Dev\DocGenerator\Command\Docs; use Google\Cloud\Dev\Release\Command\Release; +use Google\Cloud\Dev\Split\Command\Split; use Symfony\Component\Console\Application; if (!class_exists(Application::class)) { @@ -33,4 +34,5 @@ if (!class_exists(Application::class)) { $app = new Application; $app->add(new Release(__DIR__)); $app->add(new Docs(__DIR__)); +$app->add(new Split(__DIR__)); $app->run(); diff --git a/dev/sh/build-docs b/dev/sh/build-docs index cc09be67d534..1079b9de9dd6 100755 --- a/dev/sh/build-docs +++ b/dev/sh/build-docs @@ -5,7 +5,13 @@ set -ev function buildDocs () { echo "doc dir before generation:" find docs - composer google-cloud docs + + if [ -z "$TRAVIS_TAG" ]; then + ./dev/google-cloud docs + else + ./dev/google-cloud docs -r ${TRAVIS_TAG} + fi + echo "doc dir after generation:" find docs } diff --git a/dev/sh/compile-splitsh b/dev/sh/compile-splitsh new file mode 100755 index 000000000000..1cef5094d2b0 --- /dev/null +++ b/dev/sh/compile-splitsh @@ -0,0 +1,13 @@ +#!/bin/bash + +mkdir $ +export GOPATH=$TRAVIS_BUILD_DIR/go + +go get -d github.com/libgit2/git2go +cd $GOPATH/src/github.com/libgit2/git2go +git checkout next +git submodule update --init +make install + +go get github.com/splitsh/lite +go build -o $TRAVIS_BUILD_DIR/splitsh-lite github.com/splitsh/lite diff --git a/dev/sh/push-docs b/dev/sh/push-docs index a0ef3d50e770..0ee349729324 100755 --- a/dev/sh/push-docs +++ b/dev/sh/push-docs @@ -2,20 +2,11 @@ set -ev -function generateDocs () { - echo "doc dir before generation:" - find docs - composer google-cloud docs - echo "doc dir after generation:" - find docs -} - function pushDocs () { - git submodule add -q -f -b gh-pages https://${GH_OAUTH_TOKEN}@github.com/${GH_OWNER}/${GH_PROJECT_NAME} ghpages - mkdir -p ghpages/json/${1} - cp -R docs/json/master/* ghpages/json/${1} - cp docs/overview.html ghpages/json/${1} - cp docs/toc.json ghpages/json/${1} + git submodule add -q -f -b gh-pages https://${GH_OAUTH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} ghpages + + rsync -aP docs/json/* ghpages/json/ + cp docs/home.html ghpages/json cp docs/manifest.json ghpages cd ghpages @@ -25,7 +16,7 @@ function pushDocs () { git config user.email "travis@travis-ci.org" git commit -m "Updating docs for ${1}" git status - git push -q https://${GH_OAUTH_TOKEN}@github.com/${GH_OWNER}/${GH_PROJECT_NAME} HEAD:gh-pages + git push -q https://${GH_OAUTH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} HEAD:gh-pages else echo "Nothing to commit." fi diff --git a/dev/sh/split b/dev/sh/split new file mode 100755 index 000000000000..6ff7c259f9ac --- /dev/null +++ b/dev/sh/split @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +branch=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p') + +SHA=`$TRAVIS_BUILD_DIR/splitsh-lite --prefix=$1` +git push -q \ + "https://${GH_OAUTH_TOKEN}@github.com/$2" \ + $SHA:master --force diff --git a/dev/sh/trigger-split b/dev/sh/trigger-split new file mode 100755 index 000000000000..5685a6fe239e --- /dev/null +++ b/dev/sh/trigger-split @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ "$TRAVIS_JOB_NUMBER" == *.1 && -n "$TRAVIS_TAG" ]]; then + $(dirname $0)/compile-splitsh + git fetch --unshallow + composer google-cloud split +else + echo "Split occurs only in a tag run, and in the first matrix build" +fi diff --git a/dev/src/DocGenerator/Command/Docs.php b/dev/src/DocGenerator/Command/Docs.php index 33c522682c84..f4609b8a44a3 100644 --- a/dev/src/DocGenerator/Command/Docs.php +++ b/dev/src/DocGenerator/Command/Docs.php @@ -19,7 +19,9 @@ use Google\Cloud\Dev\DocGenerator\DocGenerator; use Google\Cloud\Dev\DocGenerator\GuideGenerator; +use Google\Cloud\Dev\DocGenerator\TableOfContents; use Google\Cloud\Dev\DocGenerator\TypeGenerator; +use Google\Cloud\Dev\GetComponentsTrait; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RecursiveRegexIterator; @@ -27,11 +29,17 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Docs extends Command { - const DEFAULT_OUTPUT_DIR = 'docs/json/master'; + use GetComponentsTrait; + + const DEFAULT_OUTPUT_DIR = 'docs/json'; + const TOC_SOURCE_DIR = 'docs/contents'; + const TOC_TEMPLATE = 'docs/toc.json'; + const OVERVIEW_FILE = 'docs/overview.html'; const DEFAULT_SOURCE_DIR = 'src'; private $cliBasePath; @@ -47,29 +55,121 @@ protected function configure() { $this->setName('docs') ->setDescription('Generate Documentation') - ->addArgument('source', InputArgument::OPTIONAL, 'The source directory to traverse and parse') - ->addArgument('output', InputArgument::OPTIONAL, 'The directory to output files into'); + ->addOption('release', 'r', InputOption::VALUE_REQUIRED, 'If set, docs will be generated into tag folders' . + ' such as v1.0.0 rather than master.', false) + ->addOption('pretty', 'p', InputOption::VALUE_OPTIONAL, 'If set, json files will be written with pretty'. + ' formatting using PHP\'s JSON_PRETTY_PRINT flag', false); } protected function execute(InputInterface $input, OutputInterface $output) { + $release = ($input->getOption('release') === false && $input->getOption('release') !== 'false') + ? null + : $input->getOption('release'); + + $pretty = ($input->getOption('pretty') === false) ? false : true; + $paths = [ - 'source' => ($input->getArgument('source')) - ? $this->cliBasePath .'/../'. $input->getArgument('source') - : $this->cliBasePath .'/../'. self::DEFAULT_SOURCE_DIR, + 'source' => $this->cliBasePath .'/../'. self::DEFAULT_SOURCE_DIR, + 'output' => $this->cliBasePath .'/../'. self::DEFAULT_OUTPUT_DIR, + 'project' => $this->cliBasePath .'/../', + 'manifest' => $this->cliBasePath .'/../docs/manifest.json', + 'toc' => $this->cliBasePath .'/../'. self::TOC_SOURCE_DIR, + 'tocTemplate' => $this->cliBasePath .'/../'. self::TOC_TEMPLATE, + 'overview' => $this->cliBasePath .'/../'. self::OVERVIEW_FILE + ]; + + $components = $this->getComponents($paths['source']); + $tocTemplate = json_decode(file_get_contents($paths['tocTemplate']), true); - 'output' => ($input->getArgument('output')) - ? $this->cliBasePath .'/../'. $input->getArgument('output') - : $this->cliBasePath .'/../'. self::DEFAULT_OUTPUT_DIR + foreach ($components as $component) { + $input = $paths['project'] . $component['path']; + $source = $this->getFilesList($input); + $this->generateComponentDocumentation( + $output, + $source, + $component, + $paths, + $tocTemplate, + $release, + $pretty + ); + } + + $source = $this->getFilesList($paths['project'] . '/src'); + $component = [ + 'id' => 'google-cloud', + 'path' => 'src/' ]; - $types = new TypeGenerator($paths['output']); + $this->generateComponentDocumentation( + $output, + $source, + $component, + $paths, + $tocTemplate, + $release, + $pretty, + false + ); + } + + private function generateComponentDocumentation( + OutputInterface $output, + array $source, + array $component, + array $paths, + $tocTemplate, + $release = false, + $pretty = false, + $isComponent = true + ) { + $output->writeln(sprintf('Writing documentation for %s', $component['id'])); + $output->writeln('--------------'); + + $version = $this->getComponentVersion($paths['manifest'], $component['id']); + + $outputPath = ($release) + ? $paths['output'] .'/'. $component['id'] .'/'. $version + : $paths['output'] .'/'. $component['id'] .'/master'; + + $output->writeln(sprintf('Writing to %s', realpath($outputPath))); + + $types = new TypeGenerator($outputPath); + + $docs = new DocGenerator( + $types, + $source, + $outputPath, + $this->cliBasePath, + $component['id'], + $paths['manifest'], + $release, + $isComponent + ); + + $docs->generate($component['path'], $pretty); + + $types->write($pretty); + + $output->writeln(sprintf('Writing table of contents to %s', realpath($outputPath))); + $contents = json_decode(file_get_contents($paths['toc'] .'/'. $component['id'] .'.json'), true); + + $toc = new TableOfContents( + $tocTemplate, + $component['id'], + $this->getComponentVersion($paths['manifest'], 'google-cloud'), + $paths['toc'], + $outputPath, + $release + ); + $toc->generate($pretty); - $sourceFiles = $this->getFilesList($paths['source']); - $docs = new DocGenerator($types, $sourceFiles, $paths['output'], $this->cliBasePath); - $docs->generate(); + $output->writeln(sprintf('Copying overview.html to %s', realpath($outputPath))); + copy($paths['overview'], $outputPath .'/overview.html'); - $types->write(); + $output->writeln(' '); + $output->writeln(' '); } private function getFilesList($source) diff --git a/dev/src/DocGenerator/DocGenerator.php b/dev/src/DocGenerator/DocGenerator.php index 7394002af896..5d1736a96127 100644 --- a/dev/src/DocGenerator/DocGenerator.php +++ b/dev/src/DocGenerator/DocGenerator.php @@ -32,16 +32,32 @@ class DocGenerator private $files; private $outputPath; private $executionPath; + private $componentId; + private $manifestPath; + private $release; + private $isComponent; /** * @param array $files */ - public function __construct(TypeGenerator $types, array $files, $outputPath, $executionPath) - { + public function __construct( + TypeGenerator $types, + array $files, + $outputPath, + $executionPath, + $componentId, + $manifestPath, + $release, + $isComponent = true + ) { $this->types = $types; $this->files = $files; $this->outputPath = $outputPath; $this->executionPath = $executionPath; + $this->componentId = $componentId; + $this->manifestPath = $manifestPath; + $this->release = $release; + $this->isComponent = $isComponent; } /** @@ -49,31 +65,59 @@ public function __construct(TypeGenerator $types, array $files, $outputPath, $ex * * @return void */ - public function generate() + public function generate($basePath, $pretty) { foreach ($this->files as $file) { - $currentFile = substr(str_replace($this->executionPath, '', $file), 3); + if ($basePath) { + $currentFileArr = explode($basePath, trim($file, '/')); + if (isset($currentFileArr[1])) { + $currentFile = trim($currentFileArr[1], '/'); + } + } + $isPhp = strrpos($file, '.php') == strlen($file) - strlen('.php'); if ($isPhp) { $fileReflector = new FileReflector($file); - $parser = new CodeParser($file, $currentFile, $fileReflector); + $parser = new CodeParser( + $file, + $currentFile, + $fileReflector, + dirname($this->executionPath), + $this->componentId, + $this->manifestPath, + $this->release, + $this->isComponent + ); } else { $content = file_get_contents($file); - $parser = new MarkdownParser($currentFile, $content); + $split = explode('src/', $file); + $parser = new MarkdownParser($split[1], $content); } $document = $parser->parse(); - $writer = new Writer(json_encode($document), $this->outputPath); - $writer->write(substr($currentFile, 4)); + $writer = new Writer($document, $this->outputPath, $pretty); + $writer->write($currentFile); $this->types->addType([ 'id' => $document['id'], 'title' => $document['title'], - 'contents' => $document['id'] . '.json' + 'contents' => ($this->isComponent) + ? $this->prune($document['id'] . '.json') + : $document['id'] . '.json' ]); } } + + private function prune($contentsFileName) + { + $explode = explode('/', $contentsFileName); + if (count($explode) > 1) { + array_shift($explode); + } + + return implode('/', $explode); + } } diff --git a/dev/src/DocGenerator/Parser/CodeParser.php b/dev/src/DocGenerator/Parser/CodeParser.php index cbc6616b461e..a88ed87a166c 100644 --- a/dev/src/DocGenerator/Parser/CodeParser.php +++ b/dev/src/DocGenerator/Parser/CodeParser.php @@ -18,26 +18,52 @@ namespace Google\Cloud\Dev\DocGenerator\Parser; use Google\Cloud\Dev\DocBlockStripSpaces; +use Google\Cloud\Dev\GetComponentsTrait; use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Context; use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\Tag\SeeTag; use phpDocumentor\Reflection\FileReflector; class CodeParser implements ParserInterface { + use GetComponentsTrait; + const SNIPPET_NAME_REGEX = '/\/\/\s?\[snippet\=(\w{0,})\]/'; + private static $composerFiles = []; + private $path; - private $outputName; + private $fileName; private $reflector; private $markdown; - - public function __construct($path, $outputName, FileReflector $reflector) - { + private $projectRoot; + private $externalTypes; + private $componentId; + private $manifestPath; + private $release; + private $isComponent; + + public function __construct( + $path, + $fileName, + FileReflector $reflector, + $projectRoot, + $componentId, + $manifestPath, + $release, + $isComponent = true + ) { $this->path = $path; - $this->outputName = $outputName; + $this->fileName = $fileName; $this->reflector = $reflector; $this->markdown = \Parsedown::instance(); + $this->projectRoot = $projectRoot; + $this->externalTypes = json_decode(file_get_contents(__DIR__ .'/../../../../docs/external-classes.json'), true); + $this->componentId = $componentId; + $this->manifestPath = $manifestPath; + $this->release = $release; + $this->isComponent = $isComponent; } public function parse() @@ -60,7 +86,7 @@ private function getReflector($fileReflector) return $fileReflector->getTraits()[0]; } - throw new \Exception('Could not get reflector for '. $this->outputName); + throw new \Exception('Could not get reflector for '. $this->fileName); } private function buildDocument($reflector) @@ -120,8 +146,11 @@ private function buildDescription($docBlock, $content = null) foreach ($parsedContents as &$part) { if ($part instanceof Seetag) { $reference = $part->getReference(); + if (substr_compare($reference, 'Google\Cloud', 0, 12) === 0) { $part = $this->buildLink($reference); + } elseif ($this->hasExternalType(trim(str_replace('@see', '', $part)))) { + $part = $this->buildExternalType(trim(str_replace('@see', '', $part))); } } } @@ -192,7 +221,7 @@ private function buildMethod($method) 'id' => $method->getName(), 'type' => $method->getName() === '__construct' ? 'constructor' : 'instance', 'name' => $method->getName(), - 'source' => $this->outputName . '#L' . $method->getLineNumber(), + 'source' => $this->getSource() . '#L' . $method->getLineNumber(), 'description' => $this->buildDescription($docBlock, $split['description']), 'examples' => $this->buildExamples($split['examples']), 'resources' => $this->buildResources($resources), @@ -225,7 +254,7 @@ private function buildMagicMethod($magicMethod) 'id' => $magicMethod->getMethodName(), 'type' => $magicMethod->getMethodName() === '__construct' ? 'constructor' : 'instance', 'name' => $magicMethod->getMethodName(), - 'source' => $this->outputName, + 'source' => $this->getSource(), 'description' => $this->buildDescription($docBlock, $docText), 'examples' => $this->buildExamples($examples), 'resources' => $this->buildResources($resources), @@ -415,8 +444,11 @@ private function buildReturns($returns) $returnsArray = []; foreach ($returns as $return) { + $context = $return->getDocBlock()->getContext(); + $aliases = $context ? $context->getNamespaceAliases() : []; + $returnsArray[] = [ - 'types' => $this->handleTypes($return->getTypes()), + 'types' => $this->handleTypes($return->getTypes(), $aliases), 'description' => $this->buildDescription(null, $return->getDescription()) ]; } @@ -424,44 +456,136 @@ private function buildReturns($returns) return $returnsArray; } - private function handleTypes($types) + private function handleTypes($types, array $aliases = []) { - foreach ($types as &$type) { - // object is a PHPDoc keyword so it is not capable of detecting the context - // https://github.com/phpDocumentor/ReflectionDocBlock/blob/2.0.4/src/phpDocumentor/Reflection/DocBlock/Type/Collection.php#L37 - if ($type === 'Object') { - $type = '\Google\Cloud\Storage\Object'; - } + $res = []; + foreach ($types as $type) { + $matches = []; - if (substr_compare($type, '\Google\Cloud', 0, 13) === 0) { - $type = $this->buildLink($type); - } + if (preg_match('/\\\\?(.*?)\<(.*?)\>/', $type, $matches)) { + $matches[1] = $this->resolveTypeAlias($matches[1], $aliases); + $matches[2] = $this->resolveTypeAlias($matches[2], $aliases); - $matches = []; - if (preg_match('/\\\\?Generator\<(.*?)\>/', $type, $matches)) { - $typeLink = $matches[1]; + $iteratorType = $matches[1]; if (strpos($matches[1], '\\') !== FALSE) { - $typeLink = $this->buildLink($matches[1]); + $matches[1] = $this->buildLink($matches[1]); } - $type = sprintf(htmlentities('Generator<%s>'), $typeLink); + $typeLink = $matches[2]; + if (strpos($matches[2], '\\') !== FALSE) { + $matches[2] = $this->buildLink($matches[2]); + } + + $type = sprintf(htmlentities('%s<%s>'), $matches[1], $matches[2]); + } elseif (substr_compare($type, '\\Google\\Cloud', 0, 13) === 0) { + $type = $this->buildLink($type); + } elseif ($this->hasExternalType($type)) { + $type = $this->buildExternalType($type); } + + $res[] = $type; + } + + return $res; + } + + private function resolveTypeAlias($type, array $aliases) + { + $pieces = explode('\\', $type); + $basename = array_pop($pieces); + if (array_key_exists($basename, $aliases)) { + $type = $aliases[$basename]; + } + + return $type; + } + + private function hasExternalType($type) + { + $type = trim($type, '\\'); + $types = array_filter($this->externalTypes, function ($external) use ($type) { + return (strpos($type, $external['name']) !== false); + }); + + if (count($types) === 0) { + return false; } - return $types; + return true; + } + + private function buildExternalType($type) + { + $type = trim($type, '\\'); + $types = array_values(array_filter($this->externalTypes, function ($external) use ($type) { + return (strpos($type, $external['name']) !== false); + })); + + $external = $types[0]; + + $href = sprintf($external['uri'], str_replace($external['name'], '', $type)); + return sprintf( + '%s', + $href, + $type + ); } private function buildLink($content) { - if ($content[0] === '\\') { - $content = substr($content, 1); + $componentId = null; + if ($this->isComponent && substr_compare(trim($content, '\\'), 'Google\Cloud', 0, 12) === 0) { + try { + $matches = []; + preg_match('/[Generator\<]?(Google\\\Cloud\\\[\w\\\]{0,})[\>]?[\[\]]?/', $content, $matches); + $ref = new \ReflectionClass($matches[1]); + } catch (\ReflectionException $e) { + throw new \Exception(sprintf( + 'Reflection Exception: %s in %s. Given class was %s', + $e->getMessage(), + realpath($this->path), + $content + )); + } + + $recurse = true; + $file = $ref->getFileName(); + + if (strpos($file, dirname(realpath($this->path))) !== false) { + $recurse = false; + } + + do { + $composer = dirname($file) .'/composer.json'; + if (file_exists($composer) && $component = $this->isComponent($composer)) { + $componentId = $component['id']; + if ($componentId === $this->componentId) { + $componentId = null; + } + $recurse = false; + } elseif (trim($file, '/') === trim($this->projectRoot, '/')) { + $recurse = false; + } else { + $file = dirname($file); + } + } while($recurse); } + $content = trim($content, '\\'); + $displayName = $content; $content = substr($content, 13); $parts = explode('::', $content); $type = strtolower(str_replace('\\', '/', $parts[0])); + if ($componentId) { + $version = ($this->release) + ? $this->getComponentVersion($this->manifestPath, $componentId) + : 'master'; + + $type = $componentId .'/'. $version .'/'. $type; + } + $openTag = ' $examples ]; } + + private function isComponent($composerPath) + { + if (isset(self::$composerFiles[$composerPath])) { + $contents = self::$composerFiles[$composerPath]; + } else { + $contents = json_decode(file_get_contents($composerPath), true); + self::$composerFiles[$composerPath] = $contents; + } + + if (isset($contents['extra']['component'])) { + return $contents['extra']['component']; + } + + return false; + } + + private function getSource() + { + return 'src' . explode('src', $this->path)[1]; + } } diff --git a/dev/src/DocGenerator/Parser/MarkdownParser.php b/dev/src/DocGenerator/Parser/MarkdownParser.php index 2599f80d4954..058dba85bbfe 100644 --- a/dev/src/DocGenerator/Parser/MarkdownParser.php +++ b/dev/src/DocGenerator/Parser/MarkdownParser.php @@ -49,7 +49,7 @@ public function parse() $body = $doc->getElementsByTagName('body')->item(0); return [ - 'id' => strtolower(substr($pathinfo['dirname'] .'/'. $pathinfo['filename'], 5)), + 'id' => strtolower(trim($pathinfo['dirname'] .'/'. $pathinfo['filename'], '/.')), 'type' => 'guide', 'title' => $heading->textContent, 'name' => $heading->textContent, diff --git a/dev/src/DocGenerator/TableOfContents.php b/dev/src/DocGenerator/TableOfContents.php new file mode 100644 index 000000000000..2192276f60f9 --- /dev/null +++ b/dev/src/DocGenerator/TableOfContents.php @@ -0,0 +1,84 @@ +template = $template; + $this->componentId = $componentId; + $this->componentVersion = $componentVersion; + $this->contentsPath = $contentsPath; + $this->outputPath = $outputPath; + $this->release = $release; + } + + public function generate($pretty = false) + { + $toc = $this->getToc($this->componentId); + + $tpl = $this->template; + $tpl['services'] = $this->services($toc); + $tpl['tagName'] = $this->release + ? $this->componentVersion + : 'master'; + + $writer = new Writer($tpl, $this->outputPath, $pretty); + $writer->write('toc.json'); + } + + private function services(array $toc) + { + $services = $toc['services']; + + if (isset($toc['includes'])) { + foreach ($toc['includes'] as $include) { + $toc = $this->getToc($include); + $nested = $toc['services']; + $firstService = array_shift($nested); + + $service = [ + 'title' => $toc['title'], + 'type' => $firstService['type'], + 'nav' => $nested + ]; + + if (isset($toc['pattern'])) { + $service['patterns'] = [$toc['pattern']]; + } + + $services[] = $service; + } + } + + return $services; + } + + private function getToc($componentId) + { + return json_decode(file_get_contents($this->contentsPath .'/'. $componentId .'.json'), true); + } +} diff --git a/dev/src/DocGenerator/TypeGenerator.php b/dev/src/DocGenerator/TypeGenerator.php index f05a39a6fcfc..8abfc8d4ac87 100644 --- a/dev/src/DocGenerator/TypeGenerator.php +++ b/dev/src/DocGenerator/TypeGenerator.php @@ -36,9 +36,9 @@ public function addType(array $type) $this->types[] = $type; } - public function write() + public function write($pretty = false) { - $writer = new Writer(json_encode($this->types), $this->outputPath); + $writer = new Writer($this->types, $this->outputPath, $pretty); $writer->write('types.json'); } } diff --git a/dev/src/DocGenerator/Writer.php b/dev/src/DocGenerator/Writer.php index 4cafcb9200e3..f2c22e458b63 100644 --- a/dev/src/DocGenerator/Writer.php +++ b/dev/src/DocGenerator/Writer.php @@ -21,11 +21,13 @@ class Writer { private $content; private $outputPath; + private $pretty; - public function __construct($content, $outputPath) + public function __construct(array $content, $outputPath, $pretty = false) { $this->content = $content; $this->outputPath = $outputPath; + $this->pretty = (bool) $pretty; } public function write($currentFile) @@ -33,10 +35,14 @@ public function write($currentFile) $path = $this->buildOutputPath($currentFile); if (!is_dir(dirname($path))) { - mkdir(dirname($path), 0777, true); + @mkdir(dirname($path), 0777, true); } - file_put_contents($path, $this->content); + $content = ($this->pretty) + ? json_encode($this->content, JSON_PRETTY_PRINT) + : json_encode($this->content); + + file_put_contents($path, $content); } private function buildOutputPath($currentFile) diff --git a/dev/src/Functions.php b/dev/src/Functions.php new file mode 100644 index 000000000000..fea2e9cf8d88 --- /dev/null +++ b/dev/src/Functions.php @@ -0,0 +1,29 @@ +newInstanceArgs($args); +} diff --git a/dev/src/GetComponentsTrait.php b/dev/src/GetComponentsTrait.php new file mode 100644 index 000000000000..3efa11290fe5 --- /dev/null +++ b/dev/src/GetComponentsTrait.php @@ -0,0 +1,116 @@ + 1) { + $component['prefix'] = dirname('src' . $path[1]); + } else { + $component['prefix'] = ''; + } + + $components[] = $component; + } + + return $components; + } + + private function getComponentVersion($manifestPath, $componentId) + { + $manifest = $this->getComponentManifest($manifestPath, $componentId); + return $manifest['versions'][0]; + } + + private function getComponentManifest($manifestPath, $componentId) + { + $manifest = $this->getManifest($manifestPath); + $index = $this->getManifestComponentModuleIndex($manifestPath, $manifest, $componentId); + + return $manifest['modules'][$index]; + } + + private function getManifestComponentModuleIndex($manifestPath, array $manifest, $componentId) + { + $modules = array_filter($this->getManifest($manifestPath)['modules'], function ($module) use ($componentId) { + return ($module['id'] === $componentId); + }); + + return array_keys($modules)[0]; + } + + private function getManifest($manifestPath) + { + if (self::$__manifest) { + $manifest = self::$__manifest; + } else { + $manifest = json_decode(file_get_contents($manifestPath), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Could not decode manifest json'); + } + + self::$__manifest = $manifest; + } + + return $manifest; + } + + private function getComponentComposer($componentId) + { + $components = $this->getComponents($this->components, $this->defaultComponentComposer); + + $components = array_values(array_filter($components, function ($component) use ($componentId) { + return ($component['id'] === $componentId); + })); + + if (count($components) === 0) { + throw new \InvalidArgumentException(sprintf( + 'Given component id %s is not a valid component.', + $componentId + )); + } + + return $components[0]; + } +} diff --git a/dev/src/Release/Command/Release.php b/dev/src/Release/Command/Release.php index edc3bd1f21c3..37e0b62400c8 100644 --- a/dev/src/Release/Command/Release.php +++ b/dev/src/Release/Command/Release.php @@ -17,20 +17,35 @@ namespace Google\Cloud\Dev\Release\Command; +use Google\Cloud\Dev\GetComponentsTrait; use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use vierbergenlars\SemVer\version; class Release extends Command { - const PATH_MANIFEST = 'docs/manifest.json'; - const PATH_SERVICE_BUILDER = 'src/ServiceBuilder.php'; + use GetComponentsTrait; + + const COMPONENT_BASE = '%s/../src'; + const DEFAULT_COMPONENT = 'google-cloud'; + const DEFAULT_COMPONENT_COMPOSER = '%s/../composer.json'; + const PATH_MANIFEST = '%s/../docs/manifest.json'; + const PATH_SERVICE_BUILDER = '%s/../src/ServiceBuilder.php'; private $cliBasePath; + private $defaultClient; + + private $manifest; + + private $defaultComponentComposer; + + private $components; + private $allowedReleaseTypes = [ 'major', 'minor', 'patch' ]; @@ -39,6 +54,11 @@ public function __construct($cliBasePath) { $this->cliBasePath = $cliBasePath; + $this->defaultClient = sprintf(self::PATH_SERVICE_BUILDER, $cliBasePath); + $this->manifest = sprintf(self::PATH_MANIFEST, $cliBasePath); + $this->defaultComponentComposer = sprintf(self::DEFAULT_COMPONENT_COMPOSER, $cliBasePath); + $this->components = sprintf(self::COMPONENT_BASE, $cliBasePath); + parent::__construct(); } @@ -46,23 +66,31 @@ protected function configure() { $this->setName('release') ->setDescription('Prepares a new release') - ->addArgument('version', InputArgument::REQUIRED, 'The new version number'); + ->addArgument('version', InputArgument::REQUIRED, 'The new version number.') + ->addOption( + 'component', + 'c', + InputOption::VALUE_REQUIRED, + 'The component for which the version should be updated.', + self::DEFAULT_COMPONENT + ); } protected function execute(InputInterface $input, OutputInterface $output) { + $component = $this->getComponentComposer($input->getOption('component')); + $version = $input->getArgument('version'); + + // If the version is one of "major", "minor" or "patch", determine the + // correct incrementation. if (in_array(strtolower($version), $this->allowedReleaseTypes)) { - $version = $this->getNextVersionName($version); + $version = $this->getNextVersionName($version, $component); } try { $validatedVersion = new version($version); } catch (\Exception $e) { - $validatedVersion = null; - } - - if (is_null($validatedVersion)) { throw new RuntimeException(sprintf( 'Given version %s is not a valid version name', $version @@ -72,18 +100,33 @@ protected function execute(InputInterface $input, OutputInterface $output) $version = (string) $validatedVersion; $output->writeln(sprintf( - 'Adding version %s to Documentation Manifest.', - $version + 'Adding version %s to Documentation Manifest for component %s.', + $version, + $component['id'] )); - $this->addToManifest($version); + $this->addToComponentManifest($version, $component); $output->writeln(sprintf( - 'Setting ServiceBuilder version constant to %s.', + 'Setting component version constant to %s.', $version )); - $this->updateServiceBuilder($version); + $this->updateComponentVersionConstant($version, $component); + $output->writeln(sprintf( + 'File %s VERSION constant updated to %s', + $component['entry'], + $version + )); + + if ($component['id'] !== 'google-cloud') { + $this->updateComponentVersionFile($version, $component); + $output->writeln(sprintf( + 'Component %s VERSION file updated to %s', + $component['id'], + $version + )); + } $output->writeln(sprintf( 'Release %s generated!', @@ -91,64 +134,72 @@ protected function execute(InputInterface $input, OutputInterface $output) )); } - private function getNextVersionName($type) + private function getNextVersionName($type, array $component) { - $manifest = $this->getManifest(); - $lastRelease = new version($manifest['versions'][0]); + $manifest = $this->getComponentManifest($this->manifest, $component['id']); + + if ($manifest['versions'][0] === 'master') { + $lastRelease = new version('0.0.0'); + } else { + $lastRelease = new version($manifest['versions'][0]); + } return $lastRelease->inc($type); } - private function addToManifest($version) + private function addToComponentManifest($version, array $component) { - $manifest = $this->getManifest(); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException('Could not decode manifest json'); - } + $manifest = $this->getManifest($this->manifest); + $index = $this->getManifestComponentModuleIndex($this->manifest, $manifest, $component['id']); - array_unshift($manifest['versions'], 'v'. $version); + array_unshift($manifest['modules'][$index]['versions'], 'v'. $version); $content = json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ."\n"; - $result = file_put_contents($this->getManifestPath(), $content); + $result = file_put_contents($this->manifest, $content); if (!$result) { throw new RuntimeException('File write failed'); } } - private function updateServiceBuilder($version) + private function updateComponentVersionConstant($version, array $component) { - $path = $this->cliBasePath .'/../'. self::PATH_SERVICE_BUILDER; + if (is_null($component['entry'])) { + return false; + } + + $path = $this->cliBasePath .'/../'. $component['path'] .'/'. $component['entry']; if (!file_exists($path)) { - throw new RuntimeException('ServiceBuilder not found at '. $path); + throw new \RuntimeException(sprintf( + 'Component entry file %s does not exist', + $path + )); } - $sb = file_get_contents($path); + $entry = file_get_contents($path); $replacement = sprintf("const VERSION = '%s';", $version); - $sb = preg_replace("/const VERSION = '[0-9.]{0,}'\;/", $replacement, $sb); + $entry = preg_replace("/const VERSION = [\'\\\"]([0-9.]{0,}|master)[\'\\\"]\;/", $replacement, $entry); - $result = file_put_contents($path, $sb); + $result = file_put_contents($path, $entry); if (!$result) { throw new RuntimeException('File write failed'); } + + return true; } - private function getManifest() + private function updateComponentVersionFile($version, array $component) { - $path = $this->getManifestPath(); - if (!file_exists($path)) { - throw new RuntimeException('Manifest file not found at '. $path); - } + $path = $this->cliBasePath .'/../'. $component['path'] .'/VERSION'; + $result = file_put_contents($path, $version); - return json_decode(file_get_contents($path), true); - } + if (!$result) { + throw new RuntimeException('File write failed'); + } - private function getManifestPath() - { - return $this->cliBasePath .'/../'. self::PATH_MANIFEST; + return true; } } diff --git a/dev/src/Snippet/Parser/Parser.php b/dev/src/Snippet/Parser/Parser.php index c2ec03f75674..06007b9817d1 100644 --- a/dev/src/Snippet/Parser/Parser.php +++ b/dev/src/Snippet/Parser/Parser.php @@ -176,6 +176,10 @@ public function examplesFromMethod($class, $method) $method = new ReflectionMethod($class, $method); } + if (!$method->isPublic()) { + return []; + } + $doc = new DocBlock($method); $parent = $method->getDeclaringClass(); diff --git a/dev/src/Snippet/Parser/Snippet.php b/dev/src/Snippet/Parser/Snippet.php index 74316ecaf25d..d1ace59affdf 100644 --- a/dev/src/Snippet/Parser/Snippet.php +++ b/dev/src/Snippet/Parser/Snippet.php @@ -152,9 +152,14 @@ public function invoke($returnVar = null) $cb = function($return) use ($content) { extract($this->locals); - ob_start(); - $res = eval($content ."\n\n". $return); - $out = ob_get_clean(); + try { + ob_start(); + $res = eval($content ."\n\n". $return); + $out = ob_get_clean(); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } return new InvokeResult($res, $out); }; diff --git a/dev/src/Snippet/SnippetTestCase.php b/dev/src/Snippet/SnippetTestCase.php index 98349768a57d..1bbf1e9ce293 100644 --- a/dev/src/Snippet/SnippetTestCase.php +++ b/dev/src/Snippet/SnippetTestCase.php @@ -26,18 +26,13 @@ */ class SnippetTestCase extends \PHPUnit_Framework_TestCase { - const HOOK_BEFORE = 1000; - const HOOK_AFTER = 1001; + private static $coverage; + private static $parser; - private $coverage; - private $parser; - - public function __construct() + public static function setUpBeforeClass() { - parent::__construct(); - - $this->coverage = Container::$coverage; - $this->parser = Container::$parser; + self::$coverage = Container::$coverage; + self::$parser = Container::$parser; } /** @@ -49,14 +44,14 @@ public function __construct() */ public function snippetFromClass($class, $indexOrName = 0) { - $identifier = $this->parser->createIdentifier($class, $indexOrName); + $identifier = self::$parser->createIdentifier($class, $indexOrName); - $snippet = $this->coverage->cache($identifier); + $snippet = self::$coverage->cache($identifier); if (!$snippet) { - $snippet = $this->parser->classExample($class, $indexOrName); + $snippet = self::$parser->classExample($class, $indexOrName); } - $this->coverage->cover($snippet->identifier()); + self::$coverage->cover($snippet->identifier()); return $snippet; } @@ -73,14 +68,14 @@ public function snippetFromClass($class, $indexOrName = 0) public function snippetFromMagicMethod($class, $method, $indexOrName = 0) { $name = $class .'::'. $method; - $identifier = $this->parser->createIdentifier($name, $indexOrName); + $identifier = self::$parser->createIdentifier($name, $indexOrName); - $snippet = $this->coverage->cache($identifier); + $snippet = self::$coverage->cache($identifier); if (!$snippet) { throw new \Exception('Magic Method '. $name .' was not found'); } - $this->coverage->cover($identifier); + self::$coverage->cover($identifier); return $snippet; } @@ -96,14 +91,14 @@ public function snippetFromMagicMethod($class, $method, $indexOrName = 0) public function snippetFromMethod($class, $method, $indexOrName = 0) { $name = $class .'::'. $method; - $identifier = $this->parser->createIdentifier($name, $indexOrName); + $identifier = self::$parser->createIdentifier($name, $indexOrName); - $snippet = $this->coverage->cache($identifier); + $snippet = self::$coverage->cache($identifier); if (!$snippet) { - $snippet = $this->parser->methodExample($class, $method, $indexOrName); + $snippet = self::$parser->methodExample($class, $method, $indexOrName); } - $this->coverage->cover($identifier); + self::$coverage->cover($identifier); return $snippet; } diff --git a/dev/src/Split/Command/Split.php b/dev/src/Split/Command/Split.php new file mode 100644 index 000000000000..6959e6985ac7 --- /dev/null +++ b/dev/src/Split/Command/Split.php @@ -0,0 +1,172 @@ +cliBasePath = $cliBasePath; + $this->splitShell = sprintf(self::SPLIT_SHELL, $cliBasePath); + $this->components = sprintf(self::COMPONENT_BASE, $cliBasePath); + $this->manifest = sprintf(self::PATH_MANIFEST, $cliBasePath); + + $this->http = new Client; + $this->token = getenv(self::TOKEN_ENV); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('split') + ->setDescription('Split subtree and push to various remotes.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + if (!getenv(self::TAG_ENV)) { + $output->writeln('This command should only be run inside a CI post-release process'); + return; + } + + $components = $this->getComponents($this->components); + + $tag = getenv(self::TAG_ENV); + + $parentTagSource = sprintf(self::PARENT_TAG_NAME, $tag); + + foreach ($components as $component) { + $output->writeln(''); + $output->writeln(sprintf('Starting on component %s', $component['id'])); + $output->writeln('------------'); + shell_exec(sprintf( + '%s %s %s', + $this->splitShell, + $component['prefix'], + $component['target'] + )); + + $target = $component['target']; + $matches = []; + preg_match(self::TARGET_REGEX, $target, $matches); + + $org = $matches[1]; + $repo = $matches[2]; + + $version = $this->getComponentVersion($this->manifest, $component['id']); + try { + new version($version); + } catch (SemVerException $e) { + $output->writeln(sprintf( + 'Component %s version %s is invalid.', + $component['id'], + $version + )); + continue; + } + + if ($this->doesTagExist($version, $org, $repo)) { + $output->writeln(sprintf( + 'Component %s already tagged at version %s', + $component['id'], + $version + )); + continue; + } + + $name = $component['displayName'] .' '. $version; + $notes = sprintf( + 'For release notes, please see the [associated Google Cloud PHP release](%s).', + $parentTagSource + ); + $this->createRelease($version, $org, $repo, $name, $notes); + + $output->writeln(sprintf( + 'Release %s created for component %s', + $version, + $component['id'] + )); + } + } + + private function doesTagExist($tagName, $org, $repo) + { + $res = $this->http->get(sprintf( + self::GITHUB_RELEASES_ENDPOINT, + $org, $repo, $tagName + ), [ + 'http_errors' => false, + 'auth' => [null, $this->token] + ]); + + return ($res->getStatusCode() === 200); + } + + private function createRelease($tagName, $org, $repo, $name, $notes) + { + $requestBody = [ + 'tag_name' => $tagName, + 'name' => $name, + 'body' => $notes + ]; + + $res = $this->http->post(sprintf( + self::GITHUB_RELEASE_CREATE_ENDPOINT, + $org, $repo + ), [ + 'http_errors' => false, + 'json' => $requestBody, + 'auth' => [null, $this->token] + ]); + } +} diff --git a/dev/src/StubTrait.php b/dev/src/StubTrait.php new file mode 100644 index 000000000000..7a710ed8bb72 --- /dev/null +++ b/dev/src/StubTrait.php @@ -0,0 +1,55 @@ +___getPropertyReflector($prop); + + $property->setAccessible(true); + return $property->getValue($this); + } + + public function ___setProperty($prop, $value) + { + if (!in_array($prop, json_decode($this->___props))) { + throw new \BadMethodCallException(sprintf('Property %s cannot be overloaded', $prop)); + } + + $property = $this->___getPropertyReflector($prop); + + $property->setAccessible(true); + $property->setValue($this, $value); + } + + private function ___getPropertyReflector($property) + { + $trait = new \ReflectionClass($this); + $ref = $trait->getParentClass(); + + try { + $property = $ref->getProperty($property); + } catch (\ReflectionException $e) { + throw new \BadMethodCallException($e->getMessage()); + } + + return $property; + } +} diff --git a/docs/contents/cloud-bigquery.json b/docs/contents/cloud-bigquery.json new file mode 100644 index 000000000000..5fb32b3044f9 --- /dev/null +++ b/docs/contents/cloud-bigquery.json @@ -0,0 +1,35 @@ +{ + "title": "BigQuery", + "pattern": "bigquery\/\\w{1,}", + "services": [{ + "title": "BigQueryClient", + "type": "bigquery/bigqueryclient" + }, { + "title": "Bytes", + "type": "bigquery/bytes" + }, { + "title": "Dataset", + "type": "bigquery/dataset" + }, { + "title": "Date", + "type": "bigquery/date" + }, { + "title": "InsertResponse", + "type": "bigquery/insertresponse" + }, { + "title": "Job", + "type": "bigquery/job" + }, { + "title": "QueryResults", + "type": "bigquery/queryresults" + }, { + "title": "Table", + "type": "bigquery/table" + }, { + "title": "Time", + "type": "bigquery/time" + }, { + "title": "Timestamp", + "type": "bigquery/timestamp" + }] +} diff --git a/docs/contents/cloud-core.json b/docs/contents/cloud-core.json new file mode 100644 index 000000000000..3b0b80bc4949 --- /dev/null +++ b/docs/contents/cloud-core.json @@ -0,0 +1,37 @@ +{ + "title": "Core", + "pattern": "core\/\\w{1,}", + "services": [{ + "title": "Overview", + "type": "core/readme" + }, { + "title": "IAM", + "type": "core/iam/iam", + "patterns": [ + "core/iam/\\w{1,}" + ], + "nav": [{ + "title": "PolicyBuilder", + "type": "core/iam/policybuilder" + }] + }, { + "title": "Upload", + "type": "core/upload/abstractuploader", + "patterns": [ + "core/upload/\\w{1,}" + ], + "nav": [{ + "title": "MultipartUploader", + "type": "core/upload/multipartuploader" + }, { + "title": "ResumableUploader", + "type": "core/upload/resumableuploader" + }, { + "title": "StreamableUploader", + "type": "core/upload/streamableuploader" + }] + }, { + "title": "Int64", + "type": "core/int64" + }] +} diff --git a/docs/contents/cloud-datastore.json b/docs/contents/cloud-datastore.json new file mode 100644 index 000000000000..44e37171d7bd --- /dev/null +++ b/docs/contents/cloud-datastore.json @@ -0,0 +1,33 @@ +{ + "title": "Datastore", + "pattern": "datastore\/\\w{1,}", + "services": [{ + "title": "DatastoreClient", + "type": "datastore/datastoreclient" + }, { + "title": "Transaction", + "type": "datastore/transaction" + }, { + "title": "Entity", + "type": "datastore/entity" + }, { + "title": "Key", + "type": "datastore/key" + }, { + "title": "GeoPoint", + "type": "datastore/geopoint" + }, { + "title": "Blob", + "type": "datastore/blob" + }, { + "title": "Queries", + "type": "datastore/queryinterface", + "nav": [{ + "title": "GQL Query", + "type": "datastore/query/gqlquery" + }, { + "title": "Query", + "type": "datastore/query/query" + }] + }] +} diff --git a/docs/contents/cloud-error-reporting.json b/docs/contents/cloud-error-reporting.json new file mode 100644 index 000000000000..b1801f20f9fc --- /dev/null +++ b/docs/contents/cloud-error-reporting.json @@ -0,0 +1,24 @@ +{ + "title": "Error Reporting", + "pattern": "errorreporting\/\\w{1,}", + "services": [{ + "title": "Overview", + "type": "errorreporting/readme" + }, { + "title": "v1beta1", + "type": "errorreporting/v1beta1/readme", + "patterns": [ + "errorreporting/v1beta1/\\w{1,}" + ], + "nav": [{ + "title": "ErrorGroupServiceClient", + "type": "errorreporting/v1beta1/errorgroupserviceclient" + }, { + "title": "ErrorStatsServiceClient", + "type": "errorreporting/v1beta1/errorstatsserviceclient" + }, { + "title": "ReportErrorsServiceClient", + "type": "errorreporting/v1beta1/reporterrorsserviceclient" + }] + }] +} diff --git a/docs/contents/cloud-logging.json b/docs/contents/cloud-logging.json new file mode 100644 index 000000000000..a8b8d02422ac --- /dev/null +++ b/docs/contents/cloud-logging.json @@ -0,0 +1,39 @@ +{ + "title": "Logging", + "pattern": "logging\/\\w{1,}", + "services": [{ + "title": "LoggingClient", + "type": "logging/loggingclient" + }, { + "title": "Entry", + "type": "logging/entry" + }, { + "title": "Logger", + "type": "logging/logger" + }, { + "title": "Metric", + "type": "logging/metric" + },{ + "title": "PsrLogger", + "type": "logging/psrlogger" + },{ + "title": "Sink", + "type": "logging/sink" + }, { + "title": "v2", + "type": "logging/v2/readme", + "patterns": [ + "logging/v2/\\w{1,}" + ], + "nav": [{ + "title": "ConfigServiceV2Client", + "type": "logging/v2/configservicev2client" + }, { + "title": "LoggingServiceV2Client", + "type": "logging/v2/loggingservicev2client" + }, { + "title": "MetricsServiceV2Client", + "type": "logging/v2/metricsservicev2client" + }] + }] +} diff --git a/docs/contents/cloud-monitoring.json b/docs/contents/cloud-monitoring.json new file mode 100644 index 000000000000..d6ff59d0ded5 --- /dev/null +++ b/docs/contents/cloud-monitoring.json @@ -0,0 +1,21 @@ +{ + "title": "Monitoring", + "pattern": "monitoring\/\\w{1,}", + "services": [{ + "title": "Overview", + "type": "monitoring/readme" + }, { + "title": "v3", + "type": "monitoring/v3/readme", + "patterns": [ + "monitoring/v3/\\w{1,}" + ], + "nav": [{ + "title": "GroupServiceClient", + "type": "monitoring/v3/groupserviceclient" + }, { + "title": "MetricServiceClient", + "type": "monitoring/v3/metricserviceclient" + }] + }] +} diff --git a/docs/contents/cloud-natural-language.json b/docs/contents/cloud-natural-language.json new file mode 100644 index 000000000000..6552ba78fcf3 --- /dev/null +++ b/docs/contents/cloud-natural-language.json @@ -0,0 +1,11 @@ +{ + "title": "Natural Language", + "pattern": "naturallanguage\/\\w{1,}", + "services": [{ + "title": "NaturalLanguageClient", + "type": "naturallanguage/naturallanguageclient" + }, { + "title": "Annotation", + "type": "naturallanguage/annotation" + }] +} diff --git a/docs/contents/cloud-pubsub.json b/docs/contents/cloud-pubsub.json new file mode 100644 index 000000000000..795fe56f064a --- /dev/null +++ b/docs/contents/cloud-pubsub.json @@ -0,0 +1,30 @@ +{ + "title": "Pub/Sub", + "pattern": "pubsub\/\\w{1,}", + "services": [{ + "title": "PubSubClient", + "type": "pubsub/pubsubclient" + }, { + "title": "Message", + "type": "pubsub/message" + }, { + "title": "Subscription", + "type": "pubsub/subscription" + }, { + "title": "Topic", + "type": "pubsub/topic" + }, { + "title": "v1", + "type": "pubsub/v1/readme", + "patterns": [ + "pubsub/v1/\\w{1,}" + ], + "nav": [{ + "title": "PublisherClient", + "type": "pubsub/v1/publisherclient" + }, { + "title": "SubscriberClient", + "type": "pubsub/v1/subscriberclient" + }] + }] +} diff --git a/docs/contents/cloud-speech.json b/docs/contents/cloud-speech.json new file mode 100644 index 000000000000..976f9acda989 --- /dev/null +++ b/docs/contents/cloud-speech.json @@ -0,0 +1,21 @@ +{ + "title": "Speech", + "pattern": "speech\/\\w{1,}", + "services": [{ + "title": "SpeechClient", + "type": "speech/speechclient" + }, { + "title": "Operation", + "type": "speech/operation" + }, { + "title": "v1beta1", + "type": "speech/v1beta1/readme", + "patterns": [ + "speech/v1beta1/\\w{1,}" + ], + "nav": [{ + "title": "SpeechClient", + "type": "speech/v1beta1/speechclient" + }] + }] +} diff --git a/docs/contents/cloud-storage.json b/docs/contents/cloud-storage.json new file mode 100644 index 000000000000..2105294f8fab --- /dev/null +++ b/docs/contents/cloud-storage.json @@ -0,0 +1,17 @@ +{ + "title": "Storage", + "pattern": "storage\/\\w{1,}", + "services": [{ + "title": "StorageClient", + "type": "storage/storageclient" + }, { + "title": "ACL", + "type": "storage/acl" + }, { + "title": "Bucket", + "type": "storage/bucket" + }, { + "title": "StorageObject", + "type": "storage/storageobject" + }] +} diff --git a/docs/contents/cloud-translate.json b/docs/contents/cloud-translate.json new file mode 100644 index 000000000000..4ee6a8b7c3eb --- /dev/null +++ b/docs/contents/cloud-translate.json @@ -0,0 +1,8 @@ +{ + "title": "Translation", + "pattern": "translate\/\\w{1,}", + "services": [{ + "title": "TranslateClient", + "type": "translate/translateclient" + }] +} diff --git a/docs/contents/cloud-vision.json b/docs/contents/cloud-vision.json new file mode 100644 index 000000000000..356f4d804828 --- /dev/null +++ b/docs/contents/cloud-vision.json @@ -0,0 +1,38 @@ +{ + "title": "Vision", + "pattern": "vision\/\\w{1,}", + "services": [{ + "title": "VisionClient", + "type": "vision/visionclient" + }, { + "title": "Image", + "type": "vision/image" + }, { + "title": "Annotation", + "type": "vision/annotation", + "nav": [ + { + "title": "CropHint", + "type": "vision/annotation/crophint" + }, { + "title": "Document", + "type": "vision/annotation/document" + }, { + "title": "Entity", + "type": "vision/annotation/entity" + }, { + "title": "Face", + "type": "vision/annotation/face" + }, { + "title": "ImageProperties", + "type": "vision/annotation/imageproperties" + }, { + "title": "SafeSearch", + "type": "vision/annotation/safesearch" + }, { + "title": "Web", + "type": "vision/annotation/web" + } + ] + }] +} diff --git a/docs/contents/google-cloud.json b/docs/contents/google-cloud.json new file mode 100644 index 000000000000..0da62d46cb8d --- /dev/null +++ b/docs/contents/google-cloud.json @@ -0,0 +1,21 @@ +{ + "title": "Google Cloud PHP", + "services": [{ + "title": "ServiceBuilder", + "type": "servicebuilder" + }], + "includes": [ + "cloud-bigquery", + "cloud-datastore", + "cloud-error-reporting", + "cloud-logging", + "cloud-monitoring", + "cloud-natural-language", + "cloud-pubsub", + "cloud-speech", + "cloud-storage", + "cloud-translate", + "cloud-vision", + "cloud-core" + ] +} diff --git a/docs/external-classes.json b/docs/external-classes.json new file mode 100644 index 000000000000..580897786b30 --- /dev/null +++ b/docs/external-classes.json @@ -0,0 +1,83 @@ +[{ + "name": "Google\\Auth\\", + "uri": "https://github.com/google/google-auth-library-php/blob/master/src/%s.php" +}, { + "name": "Google\\GAX\\", + "uri": "https://github.com/googleapis/gax-php/tree/master/src/%s.php" +}, { + "name": "google\\iam\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/iam/v1" +}, { + "name": "google\\devtools\\clouderrorreporting\\v1beta1\\", + "uri": "https://github.com/googleapis/proto-client-php/tree/master/src/errorreporting/v1beta1" +}, { + "name": "google\\cloud\\language\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/tree/master/src/language/v1" +}, { + "name": "google\\logging\\v2\\", + "uri": "https://github.com/googleapis/proto-client-php/tree/master/src/logging/v2" +}, { + "name": "google\\monitoring\\v3\\", + "uri": "https://github.com/googleapis/proto-client-php/tree/master/src/monitoring/v3" +}, { + "name": "google\\pubsub\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/pubsub/v1/pubsub.php" +}, { + "name": "google\\spanner\\admin\\database\\v1\\", + "name": "https://github.com/googleapis/proto-client-php/blob/master/src/spanner/admin/database/v1/spanner_database_admin.php" +}, { + "name": "google\\spanner\\admin\\instance\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/spanner/admin/instance/v1/spanner_instance_admin.php" +}, { + "name": "google\\spanner\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/spanner/v1" +}, { + "name": "google\\cloud\\speech\\v1beta1\\", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/speech/v1beta1/cloud_speech.php" +}, { + "name": "google\\devtools\\cloudtrace\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/trace/v1/trace.php" +}, { + "name": "google\\cloud\\vision\\v1\\", + "uri": "https://github.com/googleapis/proto-client-php/tree/master/src/vision/v1" +}, { + "name": "google\\longrunning\\", + "uri": "https://github.com/googleapis/gax-php/blob/master/src/generated/operations.php" +}, { + "name": "MonitoredResource", + "uri": "https://github.com/googleapis/gax-php/blob/master/src/generated/monitored_resource.php" +}, { + "name": "ServiceContextFilter", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/errorreporting/v1beta1/error_stats_service.php#L1985" +}, { + "name": "QueryTimeRange", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/errorreporting/v1beta1/error_stats_service.php#L1915" +}, { + "name": "Duration", + "uri": "https://github.com/googleapis/gax-php/blob/master/src/generated/well-known-types/duration.php" +}, { + "name": "TimedCountAlignment", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/errorreporting/v1beta1/error_stats_service.php#L8" +}, { + "name": "Timestamp", + "uri": "https://github.com/googleapis/gax-php/blob/master/src/generated/well-known-types/timestamp.php" +}, { + "name": "ErrorGroupOrder", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/errorreporting/v1beta1/error_stats_service.php#L16" +}, { + "name": "TimeInterval", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/monitoring/v3/common.php#L270" +}, { + "name": "google\\api\\MetricDescriptor", + "uri": "https://github.com/googleapis/gax-php/blob/master/src/generated/metric.php#L29" +}, { + "name": "google\\api\\MonitoredResourceDescriptor", + "uri": "https://github.com/googleapis/gax-php/blob/master/src/generated/monitored_resource.php#L8" +}, { + "name": "Aggregation", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/monitoring/v3/common.php#L429" +}, { + "name": "PushConfig", + "uri": "https://github.com/googleapis/proto-client-php/blob/master/src/pubsub/v1/pubsub.php#L1827" +}] + diff --git a/docs/home.html b/docs/home.html index 46eb81f7d874..82f9296f1687 100644 --- a/docs/home.html +++ b/docs/home.html @@ -22,7 +22,7 @@