diff --git a/packages/language/package.json b/packages/language/package.json index 0b5cf47529d..9737b3fd473 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -55,8 +55,8 @@ "@google-cloud/common": "^0.7.0", "arrify": "^1.0.1", "extend": "^3.0.0", - "google-gax": "^0.8.0", - "google-proto-files": "^0.8.3", + "google-gax": "^0.9.0", + "google-proto-files": "^0.8.5", "is": "^3.0.1", "propprop": "^0.3.1" }, diff --git a/packages/language/src/document.js b/packages/language/src/document.js index c97e2c4b922..87e35e198f2 100644 --- a/packages/language/src/document.js +++ b/packages/language/src/document.js @@ -45,7 +45,7 @@ var prop = require('propprop'); * property to pass the inline content of the document or a Storage File * object. * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @return {module:language/document} * @@ -101,6 +101,92 @@ function Document(language, config) { } } +/** + * Labels that can be used to represent a token. + * + * @private + * @type {object} + */ +Document.LABEL_DESCRIPTIONS = { + UNKNOWN: 'Unknown', + ABBREV: 'Abbreviation modifier', + ACOMP: 'Adjectival complement', + ADVCL: 'Adverbial clause modifier', + ADVMOD: 'Adverbial modifier', + AMOD: 'Adjectival modifier of an NP', + APPOS: 'Appositional modifier of an NP', + ATTR: 'Attribute dependent of a copular verb', + AUX: 'Auxiliary (non-main) verb', + AUXPASS: 'Passive auxiliary', + CC: 'Coordinating conjunction', + CCOMP: 'Clausal complement of a verb or adjective', + CONJ: 'Conjunct', + CSUBJ: 'Clausal subject', + CSUBJPASS: 'Clausal passive subject', + DEP: 'Dependency (unable to determine)', + DET: 'Determiner', + DISCOURSE: 'Discourse', + DOBJ: 'Direct object', + EXPL: 'Expletive', + GOESWITH: ' Goes with (part of a word in a text not well edited)', + IOBJ: 'Indirect object', + MARK: 'Marker (word introducing a subordinate clause)', + MWE: 'Multi-word expression', + MWV: 'Multi-word verbal expression', + NEG: 'Negation modifier', + NN: 'Noun compound modifier', + NPADVMOD: 'Noun phrase used as an adverbial modifier', + NSUBJ: 'Nominal subject', + NSUBJPASS: 'Passive nominal subject', + NUM: 'Numeric modifier of a noun', + NUMBER: 'Element of compound number', + P: 'Punctuation mark', + PARATAXIS: 'Parataxis relation', + PARTMOD: 'Participial modifier', + PCOMP: 'The complement of a preposition is a clause', + POBJ: 'Object of a preposition', + POSS: 'Possession modifier', + POSTNEG: 'Postverbal negative particle', + PRECOMP: 'Predicate complement', + PRECONJ: 'Preconjunt', + PREDET: 'Predeterminer', + PREF: 'Prefix', + PREP: 'Prepositional modifier', + PRONL: 'The relationship between a verb and verbal morpheme', + PRT: 'Particle', + PS: 'Associative or possessive marker', + QUANTMOD: 'Quantifier phrase modifier', + RCMOD: 'Relative clause modifier', + RCMODREL: 'Complementizer in relative clause', + RDROP: 'Ellipsis without a preceding predicate', + REF: 'Referent', + REMNANT: 'Remnant', + REPARANDUM: 'Reparandum', + ROOT: 'Root', + SNUM: 'Suffix specifying a unit of number', + SUFF: 'Suffix', + TMOD: 'Temporal modifier', + TOPIC: 'Topic marker', + VMOD: 'Clause headed by an infinite form of the verb that modifies a noun', + VOCATIVE: 'Vocative', + XCOMP: 'Open clausal complement', + SUFFIX: 'Name suffix', + TITLE: 'Name title', + ADVPHMOD: 'Adverbial phrase modifier', + AUXCAUS: 'Causative auxiliary', + AUXVV: 'Helper auxiliary', + DTMOD: 'Rentaishi (Prenominal modifier)', + FOREIGN: 'Foreign words', + KW: 'Keyword', + LIST: 'List for chains of comparable items', + NOMC: 'Nominalized clause', + NOMCSUBJ: 'Nominalized clausal subject', + NOMCSUBJPASS: 'Nominalized clausal passive', + NUMC: 'Compound of numeric modifier', + COP: 'Copula', + DISLOCATED: 'Dislocated relation (for fronted/topicalized elements)' +}; + /** * The parts of speech that will be recognized by the Natural Language API. * @@ -140,11 +226,12 @@ Document.PART_OF_SPEECH = { * * - {module:language#detectEntities} * - {module:language#detectSentiment} + * - {module:language#detectSyntax} * - * @resource [documents.annotateText API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText} + * @resource [documents.annotateText API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/annotateText} * * @param {object=} options - Configuration object. See - * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText#request-body). + * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1/documents/annotateText#request-body). * @param {boolean} options.entities - Detect the entities from this document. * By default, all features (`entities`, `sentiment`, and `syntax`) are * enabled. By overriding any of these values, all defaults are switched to @@ -196,13 +283,34 @@ Document.PART_OF_SPEECH = { * text. * @param {object[]} callback.annotation.tokens - Parts of speech that were * detected from the text. - * @param {object[]} callback.annotation.tokens.text - The piece of text + * @param {string} callback.annotation.tokens[].text - The piece of text * analyzed. - * @param {object[]} callback.annotation.tokens.partOfSpeech - A description of + * @param {string} callback.annotation.tokens[].partOfSpeech - A description of * the part of speech (`Noun (common and propert)`, * `Verb (all tenses and modes)`, etc.). - * @param {object[]} callback.annotation.tokens.partOfSpeechTag - A short code + * @param {string} callback.annotation.tokens[].tag - A short code * for the type of speech (`NOUN`, `VERB`, etc.). + * @param {string} callback.annotations.tokens[].aspect - The characteristic of + * a verb that expresses time flow during an event. + * @param {string} callback.annotations.tokens[].case - The grammatical function + * performed by a noun or pronoun in a phrase, clause, or sentence. + * @param {string} callback.annotations.tokens[].form - Form categorizes + * different forms of verbs, adjectives, adverbs, etc. + * @param {string} callback.annotations.tokens[].gender - Gender classes of + * nouns reflected in the behaviour of associated words + * @param {string} callback.annotations.tokens[].mood - The grammatical feature + * of verbs, used for showing modality and attitude. + * @param {string} callback.annotations.tokens[].number - Count distinctions. + * @param {string} callback.annotations.tokens[].person - The distinction + * between the speaker, second person, third person, etc. + * @param {string} callback.annotations.tokens[].proper - This category shows if + * the token is part of a proper name. + * @param {string} callback.annotations.tokens[].reciprocity - Reciprocal + * features of a pronoun + * @param {string} callback.annotations.tokens[].tense - Time reference. + * @param {string} callback.annotations.tokens[].voice - The relationship + * between the action that a verb expresses and the participants identified + * by its arguments. * @param {object} callback.apiResponse - The full API response. * * @example @@ -230,12 +338,34 @@ Document.PART_OF_SPEECH = { * // { * // text: 'Google', * // partOfSpeech: 'Noun (common and proper)', - * // partOfSpeechTag: 'NOUN' + * // tag: 'NOUN', + * // aspect: 'PERFECTIVE', + * // case: 'ADVERBIAL', + * // form: 'ADNOMIAL', + * // gender: 'FEMININE', + * // mood: 'IMPERATIVE', + * // number: 'SINGULAR', + * // person: 'FIRST', + * // proper: 'PROPER', + * // reciprocity: 'RECIPROCAL', + * // tense: 'PAST', + * // voice: 'PASSIVE' * // }, * // { * // text: 'is', * // partOfSpeech: 'Verb (all tenses and modes)', - * // partOfSpeechTag: 'VERB' + * // tag: 'VERB', + * // aspect: 'PERFECTIVE', + * // case: 'ADVERBIAL', + * // form: 'ADNOMIAL', + * // gender: 'FEMININE', + * // mood: 'IMPERATIVE', + * // number: 'SINGULAR', + * // person: 'FIRST', + * // proper: 'PROPER', + * // reciprocity: 'RECIPROCAL', + * // tense: 'PAST', + * // voice: 'PASSIVE' * // }, * // ... * // ] @@ -284,8 +414,8 @@ Document.PART_OF_SPEECH = { * // annotation = { * // language: 'en', * // sentiment: { - * // polarity: 100, - * // magnitude: 40 + * // score: 100, + * // magnitude: 4 * // }, * // entities: { * // organizations: [ @@ -301,7 +431,8 @@ Document.PART_OF_SPEECH = { * // text: { * // content: 'Google', * // beginOffset: -1 - * // } + * // }, + * // type: 'PROPER' * // } * // ] * // } @@ -320,7 +451,8 @@ Document.PART_OF_SPEECH = { * // { * // content: 'American', * // beginOffset: -1 - * // } + * // }, + * // type: 'PROPER' * // ] * // } * // ] @@ -329,10 +461,12 @@ Document.PART_OF_SPEECH = { * // }, * // sentences: [ * // { - * // content: - * // 'Google is an American multinational technology company' + - * // 'specializing in Internet-related services and products.' - * // beginOffset: -1 + * // text: { + * // content: + * // 'Google is an American multinational technology company' + + * // 'specializing in Internet-related services and products.', + * // beginOffset: -1 + * // } * // } * // ], * // tokens: [ @@ -342,11 +476,23 @@ Document.PART_OF_SPEECH = { * // beginOffset: -1 * // }, * // partOfSpeech: { - * // tag: 'NOUN' + * // tag: 'NOUN', + * // aspect: 'PERFECTIVE', + * // case: 'ADVERBIAL', + * // form: 'ADNOMIAL', + * // gender: 'FEMININE', + * // mood: 'IMPERATIVE', + * // number: 'SINGULAR', + * // person: 'FIRST', + * // proper: 'PROPER', + * // reciprocity: 'RECIPROCAL', + * // tense: 'PAST', + * // voice: 'PASSIVE' * // }, * // dependencyEdge: { * // headTokenIndex: 1, - * // label: 'NSUBJ' + * // label: 'NSUBJ', + * // description: 'Nominal subject' * // }, * // lemma: 'Google' * // }, @@ -434,10 +580,10 @@ Document.prototype.annotate = function(options, callback) { /** * Detect entities from the document. * - * @resource [documents.analyzeEntities API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeEntities} + * @resource [documents.analyzeEntities API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeEntities} * * @param {object=} options - Configuration object. See - * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeEntities#request-body). + * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeEntities#request-body). * @param {boolean} options.verbose - Enable verbose mode for more detailed * results. Default: `false` * @param {function} callback - The callback function. @@ -567,10 +713,10 @@ Document.prototype.detectEntities = function(options, callback) { /** * Detect the sentiment of the text in this document. * - * @resource [documents.analyzeSentiment API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeSentiment} + * @resource [documents.analyzeSentiment API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSentiment} * * @param {object=} options - Configuration object. See - * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeSentiment#request-body). + * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSentiment#request-body). * @param {boolean} options.verbose - Enable verbose mode for more detailed * results. Default: `false` * @param {function} callback - The callback function. @@ -601,8 +747,19 @@ Document.prototype.detectEntities = function(options, callback) { * } * * // sentiment = { - * // polarity: 100, - * // magnitude: 40 + * // score: 100, + * // magnitude: 4, + * // sentences: [ + * // { + * // text: { + * // content: + * // 'Google is an American multinational technology company' + + * // 'specializing in Internet-related services and products.', + * // beginOffset: -1 + * // } + * // } + * // ], + * // language: 'en' * // } * }); * @@ -634,10 +791,186 @@ Document.prototype.detectSentiment = function(options, callback) { var originalResp = extend(true, {}, resp); var sentiment = Document.formatSentiment_(resp.documentSentiment, verbose); + if (verbose) { + sentiment = extend(sentiment, { + sentences: Document.formatSentences_(resp.sentences, verbose), + language: resp.language + }); + } + callback(null, sentiment, originalResp); }); }; +/** + * Detect syntax from the document. + * + * @resource [documents.analyzeSyntax API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSyntax} + * + * @param {object=} options - Configuration object. See + * [documents.annotateSyntax](https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSyntax#request-body). + * @param {boolean} options.verbose - Enable verbose mode for more detailed + * results. Default: `false` + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error occurred while making this request. + * @param {object} callback.syntax - The syntax recognized from the text. + * @param {string} callback.syntax.language - The language detected from the + * text. + * @param {string[]} callback.syntax.sentences - Sentences detected from the + * text. + * @param {object[]} callback.syntax.tokens - Parts of speech that were + * detected from the text. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * document.detectSyntax(function(err, syntax) { + * if (err) { + * // Error handling omitted. + * } + * + * // syntax = { + * // sentences: [ + * // 'Google is an American multinational technology company ' + + * // 'specializing in Internet-related services and products.' + * // ], + * // tokens: [ + * // { + * // text: 'Google', + * // partOfSpeech: 'Noun (common and proper)', + * // tag: 'NOUN', + * // aspect: 'PERFECTIVE', + * // case: 'ADVERBIAL', + * // form: 'ADNOMIAL', + * // gender: 'FEMININE', + * // mood: 'IMPERATIVE', + * // number: 'SINGULAR', + * // person: 'FIRST', + * // proper: 'PROPER', + * // reciprocity: 'RECIPROCAL', + * // tense: 'PAST', + * // voice: 'PASSIVE' + * // }, + * // { + * // text: 'is', + * // partOfSpeech: 'Verb (all tenses and modes)', + * // tag: 'VERB', + * // aspect: 'PERFECTIVE', + * // case: 'ADVERBIAL', + * // form: 'ADNOMIAL', + * // gender: 'FEMININE', + * // mood: 'IMPERATIVE', + * // number: 'SINGULAR', + * // person: 'FIRST', + * // proper: 'PROPER', + * // reciprocity: 'RECIPROCAL', + * // tense: 'PAST', + * // voice: 'PASSIVE' + * // }, + * // ... + * // ], + * // language: 'en' + * // } + * }); + * + * //- + * // Verbose mode may also be enabled for more detailed results. + * //- + * var options = { + * verbose: true + * }; + * + * document.detectSyntax(options, function(err, syntax) { + * if (err) { + * // Error handling omitted. + * } + * + * // syntax = { + * // sentences: [ + * // { + * // text: { + * // content: + * // 'Google is an American multinational technology company' + + * // 'specializing in Internet-related services and products.', + * // beginOffset: -1 + * // }, + * // sentiment: { + * // score: 100 + * // magnitude: 4 + * // } + * // } + * // ], + * // tokens: [ + * // { + * // text: { + * // content: 'Google', + * // beginOffset: -1 + * // }, + * // partOfSpeech: { + * // tag: 'NOUN', + * // aspect: 'PERFECTIVE', + * // case: 'ADVERBIAL', + * // form: 'ADNOMIAL', + * // gender: 'FEMININE', + * // mood: 'IMPERATIVE', + * // number: 'SINGULAR', + * // person: 'FIRST', + * // proper: 'PROPER', + * // reciprocity: 'RECIPROCAL', + * // tense: 'PAST', + * // voice: 'PASSIVE' + * // }, + * // dependencyEdge: { + * // headTokenIndex: 1, + * // label: 'NSUBJ', + * // description: 'Nominal subject' + * // }, + * // lemme: 'Google' + * // } + * // ], + * // language: 'en' + * // } + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * document.detectSyntax().then(function(data) { + * var syntax = data[0]; + * var apiResponse = data[1]; + * }); + */ +Document.prototype.detectSyntax = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + var verbose = options.verbose === true; + + this.api.Language.analyzeSyntax({ + document: this.document, + encodingType: this.encodingType + }, function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } + + var originalResp = extend(true, {}, resp); + var syntax = Document.formatTokens_(resp.tokens, verbose); + + if (verbose) { + syntax = { + tokens: syntax, + sentences: Document.formatSentences_(resp.sentences, verbose), + language: resp.language + }; + } + + callback(null, syntax, originalResp); + }); +}; + /** * Take a raw response from the API and make it more user-friendly. * @@ -698,10 +1031,8 @@ Document.formatEntities_ = function(entities, verbose) { * objects in verbose mode. */ Document.formatSentences_ = function(sentences, verbose) { - sentences = sentences.map(prop('text')); - if (!verbose) { - sentences = sentences.map(prop('content')); + sentences = sentences.map(prop('text')).map(prop('content')); } return sentences; @@ -716,17 +1047,17 @@ Document.formatSentences_ = function(sentences, verbose) { * API. * @param {boolean} verbose - Enable verbose mode for more detailed results. * @return {number|object} - The sentiment represented as a number in the range - * of `-100` to `100` or an object containing `polarity` and `magnitude` + * of `-100` to `100` or an object containing `score` and `magnitude` * measurements in verbose mode. */ Document.formatSentiment_ = function(sentiment, verbose) { sentiment = { - polarity: sentiment.polarity *= 100, - magnitude: sentiment.magnitude *= 100 + score: sentiment.score *= 100, + magnitude: sentiment.magnitude }; if (!verbose) { - sentiment = sentiment.polarity; + sentiment = sentiment.score; } return sentiment; @@ -745,12 +1076,27 @@ Document.formatSentiment_ = function(sentiment, verbose) { */ Document.formatTokens_ = function(tokens, verbose) { if (!verbose) { - tokens = tokens.map(function(token) { - return { - text: token.text.content, - partOfSpeech: Document.PART_OF_SPEECH[token.partOfSpeech.tag], - partOfSpeechTag: token.partOfSpeech.tag - }; + tokens = tokens.map(function(rawToken) { + var token = extend({}, rawToken.partOfSpeech, { + text: rawToken.text.content, + partOfSpeech: Document.PART_OF_SPEECH[rawToken.partOfSpeech.tag] + }); + + if (rawToken.dependencyEdge) { + var label = rawToken.dependencyEdge.label; + + token.dependencyEdge = extend({}, rawToken.dependencyEdge, { + description: Document.LABEL_DESCRIPTIONS[label] + }); + } + + for (var part in token) { + if (token.hasOwnProperty(part) && /UNKNOWN/.test(token[part])) { + token[part] = undefined; + } + } + + return token; }); } diff --git a/packages/language/src/index.js b/packages/language/src/index.js index 07c92e80c05..21588c04288 100644 --- a/packages/language/src/index.js +++ b/packages/language/src/index.js @@ -23,7 +23,7 @@ var common = require('@google-cloud/common'); var extend = require('extend'); var is = require('is'); -var v1beta1 = require('./v1beta1'); +var v1 = require('./v1'); /** * @type {module:language/document} @@ -43,12 +43,12 @@ var Document = require('./document.js'); * including sentiment analysis, entity recognition, and syntax analysis. This * API is part of the larger Cloud Machine Learning API. * - * The Cloud Natural Language API currently supports English for - * [sentiment analysis](https://cloud.google.com/natural-language/docs/reference/rest/v1beta1/documents/analyzeSentiment) - * and English, Spanish, and Japanese for - * [entity analysis](https://cloud.google.com/natural-language/docs/reference/rest/v1beta1/documents/analyzeEntities) + * The Cloud Natural Language API currently supports English, Spanish, and + * Japanese for + * [sentiment analysis](https://cloud.google.com/natural-language/docs/reference/rest/v1/documents/analyzeSentiment), + * [entity analysis](https://cloud.google.com/natural-language/docs/reference/rest/v1/documents/analyzeEntities) * and - * [syntax analysis](https://cloud.google.com/natural-language/docs/reference/rest/v1beta1/documents/annotateText). + * [syntax analysis](https://cloud.google.com/natural-language/docs/reference/rest/v1/documents/annotateText). * * @constructor * @alias module:language @@ -64,7 +64,7 @@ function Language(options) { } this.api = { - Language: v1beta1(options).languageServiceApi(options) + Language: v1(options).languageServiceApi(options) }; } @@ -76,14 +76,14 @@ function Language(options) { * detection, this may be more convenient. However, if you plan to run multiple * detections, it's easier to create a {module:language/document} object. * - * @resource [documents.annotate API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText} + * @resource [documents.annotate API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/annotateText} * * @param {string|module:storage/file} content - Inline content or a Storage * File object. * @param {object=} options - Configuration object. See - * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText#request-body). + * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1/documents/annotateText#request-body). * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @param {string} options.type - The type of document, either `html` or `text`. * @param {boolean} options.verbose - Enable verbose mode for more detailed @@ -168,14 +168,14 @@ Language.prototype.annotate = function(content, options, callback) { * detection, this may be more convenient. However, if you plan to run multiple * detections, it's easier to create a {module:language/document} object. * - * @resource [documents.analyzeEntities API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeEntities} + * @resource [documents.analyzeEntities API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeEntities} * * @param {string|module:storage/file} content - Inline content or a Storage * File object. * @param {object=} options - Configuration object. See - * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeEntities#request-body). + * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeEntities#request-body). * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @param {string} options.type - The type of document, either `html` or `text`. * @param {boolean} options.verbose - Enable verbose mode for more detailed @@ -259,14 +259,14 @@ Language.prototype.detectEntities = function(content, options, callback) { * detection, this may be more convenient. However, if you plan to run multiple * detections, it's easier to create a {module:language/document} object. * - * @resource [documents.analyzeSentiment API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeSentiment} + * @resource [documents.analyzeSentiment API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSentiment} * * @param {string|module:storage/file} content - Inline content or a Storage * File object. * @param {object=} options - Configuration object. See - * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/analyzeSentiment#request-body). + * [documents.annotateText](https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSentiment#request-body). * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @param {string} options.type - The type of document, either `html` or `text`. * @param {boolean} options.verbose - Enable verbose mode for more detailed @@ -333,6 +333,97 @@ Language.prototype.detectSentiment = function(content, options, callback) { document.detectSentiment(options, callback); }; +/** + * Detect the syntax of a block of text. + * + * NOTE: This is a convenience method which doesn't require creating a + * {module:language/document} object. If you are only running a single + * detection, this may be more convenient. However, if you plan to run multiple + * detections, it's easier to create a {module:language/document} object. + * + * @resource [documents.analyzeSyntax API Documentation]{@link https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSyntax} + * + * @param {string|module:storage/file} content - Inline content or a Storage + * File object. + * @param {object=} options - Configuration object. See + * [documents.analyzeSyntax](https://cloud.google.com/natural-language/reference/rest/v1/documents/analyzeSyntax#request-body). + * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). + * @param {string} options.language - The language of the text. + * @param {string} options.type - The type of document, either `html` or `text`. + * @param {boolean} options.verbose - Enable verbose mode for more detailed + * results. Default: `false` + * @param {function} callback - See {module:language/document#detectSyntax}. + * + * @example + * //- + * // See {module:language/document#detectSyntax} for a detailed breakdown of + * // the arguments your callback will be executed with. + * //- + * function callback(err, syntax, apiResponse) {} + * + * language.detectSyntax('Axel Foley is from Detroit', callback); + * + * //- + * // Or, provide a reference to a file hosted on Google Cloud Storage. + * //- + * var gcs = require('@google-cloud/storage')({ + * projectId: 'grape-spaceship-123' + * }); + * var bucket = gcs.bucket('my-bucket'); + * var file = bucket.file('my-file'); + * + * language.detectSyntax(file, callback); + * + * //- + * // Specify HTML content. + * //- + * var options = { + * type: 'html' + * }; + * + * language.detectSyntax('Axel Foley is from Detroit', options, callback); + * + * //- + * // Specify the language the text is written in. + * //- + * var options = { + * language: 'es' + * }; + * + * language.detectSyntax('Axel Foley es de Detroit', options, callback); + * + * //- + * // Verbose mode may also be enabled for more detailed results. + * //- + * var options = { + * verbose: true + * }; + * + * language.detectSyntax('Axel Foley is from Detroit', options, callback); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * language.detectSyntax('Axel Foley is from Detroit').then(function(data) { + * var syntax = data[0]; + * var apiResponse = data[1]; + * }); + */ +Language.prototype.detectSyntax = function(content, options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = extend({}, options, { + content: content + }); + + var document = this.document(options); + document.detectSyntax(options, callback); +}; + /** * Create a Document object for an unknown type. If you know the type, use the * appropriate method below: @@ -347,7 +438,7 @@ Language.prototype.detectSentiment = function(content, options, callback) { * property to pass the inline content of the document or a Storage File * object. * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @return {module:language/document} * @@ -392,7 +483,7 @@ Language.prototype.document = function(config) { * Storage File object. * @param {object=} options - Configuration object. * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @return {module:language/document} * @@ -442,7 +533,7 @@ Language.prototype.html = function(content, options) { * Storage File object. * @param {object=} options - Configuration object. * @param {string} options.encoding - `UTF8`, `UTF16`, or `UTF32`. See - * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1beta1/EncodingType). + * [`EncodingType`](https://cloud.google.com/natural-language/reference/rest/v1/EncodingType). * @param {string} options.language - The language of the text. * @return {module:language/document} * @@ -494,4 +585,4 @@ common.util.promisifyAll(Language, { }); module.exports = Language; -module.exports.v1beta1 = v1beta1; +module.exports.v1 = v1; diff --git a/packages/language/src/v1beta1/index.js b/packages/language/src/v1/index.js similarity index 81% rename from packages/language/src/v1beta1/index.js rename to packages/language/src/v1/index.js index 2a00e520c12..033602be4d4 100644 --- a/packages/language/src/v1beta1/index.js +++ b/packages/language/src/v1/index.js @@ -19,14 +19,14 @@ var languageServiceApi = require('./language_service_api'); var extend = require('extend'); var gax = require('google-gax'); -function v1beta1(options) { +function v1(options) { options = extend({ - scopes: v1beta1.ALL_SCOPES + scopes: v1.ALL_SCOPES }, options); var gaxGrpc = gax.grpc(options); return languageServiceApi(gaxGrpc); } -v1beta1.SERVICE_ADDRESS = languageServiceApi.SERVICE_ADDRESS; -v1beta1.ALL_SCOPES = languageServiceApi.ALL_SCOPES; -module.exports = v1beta1; +v1.SERVICE_ADDRESS = languageServiceApi.SERVICE_ADDRESS; +v1.ALL_SCOPES = languageServiceApi.ALL_SCOPES; +module.exports = v1; diff --git a/packages/language/src/v1beta1/language_service_api.js b/packages/language/src/v1/language_service_api.js similarity index 76% rename from packages/language/src/v1beta1/language_service_api.js rename to packages/language/src/v1/language_service_api.js index 26182642f7f..dc0aa8e71ec 100644 --- a/packages/language/src/v1beta1/language_service_api.js +++ b/packages/language/src/v1/language_service_api.js @@ -15,7 +15,7 @@ * * EDITING INSTRUCTIONS * This file was generated from the file - * https://github.com/googleapis/googleapis/blob/master/google/cloud/language/v1beta1/language_service.proto, + * https://github.com/googleapis/googleapis/blob/master/google/cloud/language/v1/language_service.proto, * and updates to that file get reflected here through a refresh process. * For the short term, the refresh process will only be runnable by Google * engineers. @@ -37,7 +37,6 @@ var DEFAULT_SERVICE_PORT = 443; var CODE_GEN_NAME_VERSION = 'gapic/0.1.0'; - /** * The scopes needed to make gRPC calls to all of the methods defined in * this service. @@ -55,10 +54,10 @@ var ALL_SCOPES = [ * @see {@link languageServiceApi} * * @example - * var languageV1beta1 = require('@google-cloud/language').v1beta1({ + * var languageV1 = require('@google-cloud/language').v1({ * // optional auth parameters. * }); - * var api = languageV1beta1.languageServiceApi(); + * var api = languageV1.languageServiceApi(); * * @class */ @@ -78,21 +77,20 @@ function LanguageServiceApi(gaxGrpc, grpcClients, opts) { 'nodejs/' + process.version].join(' '); var defaults = gaxGrpc.constructSettings( - 'google.cloud.language.v1beta1.LanguageService', + 'google.cloud.language.v1.LanguageService', configData, clientConfig, - null, - null, {'x-goog-api-client': googleApiClient}); var languageServiceStub = gaxGrpc.createStub( servicePath, port, - grpcClients.languageServiceClient.google.cloud.language.v1beta1.LanguageService, + grpcClients.languageServiceClient.google.cloud.language.v1.LanguageService, {sslCreds: sslCreds}); var languageServiceStubMethods = [ 'analyzeSentiment', 'analyzeEntities', + 'analyzeSyntax', 'annotateText' ]; languageServiceStubMethods.forEach(function(methodName) { @@ -100,7 +98,8 @@ function LanguageServiceApi(gaxGrpc, grpcClients, opts) { languageServiceStub.then(function(languageServiceStub) { return languageServiceStub[methodName].bind(languageServiceStub); }), - defaults[methodName]); + defaults[methodName], + null); }.bind(this)); } @@ -116,19 +115,23 @@ function LanguageServiceApi(gaxGrpc, grpcClients, opts) { * ({@link Document.language}="EN"). * * This object should have the same structure as [Document]{@link Document} + * @param {number=} request.encodingType + * The encoding type used by the API to calculate sentence offsets. + * + * The number should be among the values of [EncodingType]{@link EncodingType} * @param {Object=} options * Optional parameters. You can override the default settings for this call, e.g, timeout, * retries, paginations, etc. See [gax.CallOptions]{@link https://googleapis.github.io/gax-nodejs/global.html#CallOptions} for the details. * @param {function(?Error, ?Object)=} callback * The function which will be called with the result of the API call. * - * The second parameter to the callback is an object representing [AnalyzeSentimentResponse]{@link AnalyzeSentimentResponse} + * The second parameter to the callback is an object representing [AnalyzeSentimentResponse]{@link AnalyzeSentimentResponse}. * @returns {Promise} - The promise which resolves to the response object. * The promise has a method named "cancel" which cancels the ongoing API call. * * @example * - * var api = languageV1beta1.languageServiceApi(); + * var api = languageV1.languageServiceApi(); * var document = {}; * api.analyzeSentiment({document: document}).then(function(response) { * // doThingsWith(response) @@ -167,13 +170,13 @@ LanguageServiceApi.prototype.analyzeSentiment = function(request, options, callb * @param {function(?Error, ?Object)=} callback * The function which will be called with the result of the API call. * - * The second parameter to the callback is an object representing [AnalyzeEntitiesResponse]{@link AnalyzeEntitiesResponse} + * The second parameter to the callback is an object representing [AnalyzeEntitiesResponse]{@link AnalyzeEntitiesResponse}. * @returns {Promise} - The promise which resolves to the response object. * The promise has a method named "cancel" which cancels the ongoing API call. * * @example * - * var api = languageV1beta1.languageServiceApi(); + * var api = languageV1.languageServiceApi(); * var document = {}; * var encodingType = EncodingType.NONE; * var request = { @@ -198,10 +201,59 @@ LanguageServiceApi.prototype.analyzeEntities = function(request, options, callba }; /** - * Advanced API that analyzes the document and provides a full set of text - * annotations, including semantic, syntactic, and sentiment information. This - * API is intended for users who are familiar with machine learning and need - * in-depth text features to build upon. + * Analyzes the syntax of the text and provides sentence boundaries and + * tokenization along with part of speech tags, dependency trees, and other + * properties. + * + * @param {Object} request + * The request object that will be sent. + * @param {Object} request.document + * Input document. + * + * This object should have the same structure as [Document]{@link Document} + * @param {number} request.encodingType + * The encoding type used by the API to calculate offsets. + * + * The number should be among the values of [EncodingType]{@link EncodingType} + * @param {Object=} options + * Optional parameters. You can override the default settings for this call, e.g, timeout, + * retries, paginations, etc. See [gax.CallOptions]{@link https://googleapis.github.io/gax-nodejs/global.html#CallOptions} for the details. + * @param {function(?Error, ?Object)=} callback + * The function which will be called with the result of the API call. + * + * The second parameter to the callback is an object representing [AnalyzeSyntaxResponse]{@link AnalyzeSyntaxResponse}. + * @returns {Promise} - The promise which resolves to the response object. + * The promise has a method named "cancel" which cancels the ongoing API call. + * + * @example + * + * var api = languageV1.languageServiceApi(); + * var document = {}; + * var encodingType = EncodingType.NONE; + * var request = { + * document: document, + * encodingType: encodingType + * }; + * api.analyzeSyntax(request).then(function(response) { + * // doThingsWith(response) + * }).catch(function(err) { + * console.error(err); + * }); + */ +LanguageServiceApi.prototype.analyzeSyntax = function(request, options, callback) { + if (options instanceof Function && callback === undefined) { + callback = options; + options = {}; + } + if (options === undefined) { + options = {}; + } + return this._analyzeSyntax(request, options, callback); +}; + +/** + * A convenience method that provides all the features that analyzeSentiment, + * analyzeEntities, and analyzeSyntax provide in one call. * * @param {Object} request * The request object that will be sent. @@ -223,13 +275,13 @@ LanguageServiceApi.prototype.analyzeEntities = function(request, options, callba * @param {function(?Error, ?Object)=} callback * The function which will be called with the result of the API call. * - * The second parameter to the callback is an object representing [AnnotateTextResponse]{@link AnnotateTextResponse} + * The second parameter to the callback is an object representing [AnnotateTextResponse]{@link AnnotateTextResponse}. * @returns {Promise} - The promise which resolves to the response object. * The promise has a method named "cancel" which cancels the ongoing API call. * * @example * - * var api = languageV1beta1.languageServiceApi(); + * var api = languageV1.languageServiceApi(); * var document = {}; * var features = {}; * var encodingType = EncodingType.NONE; @@ -262,9 +314,9 @@ function LanguageServiceApiBuilder(gaxGrpc) { var languageServiceClient = gaxGrpc.load([{ root: require('google-proto-files')('..'), - file: 'google/cloud/language/v1beta1/language_service.proto' + file: 'google/cloud/language/v1/language_service.proto' }]); - extend(this, languageServiceClient.google.cloud.language.v1beta1); + extend(this, languageServiceClient.google.cloud.language.v1); var grpcClients = { languageServiceClient: languageServiceClient diff --git a/packages/language/src/v1beta1/language_service_client_config.json b/packages/language/src/v1/language_service_client_config.json similarity index 84% rename from packages/language/src/v1beta1/language_service_client_config.json rename to packages/language/src/v1/language_service_client_config.json index 6ac3a7004ff..5c946b6bc60 100644 --- a/packages/language/src/v1beta1/language_service_client_config.json +++ b/packages/language/src/v1/language_service_client_config.json @@ -1,6 +1,6 @@ { "interfaces": { - "google.cloud.language.v1beta1.LanguageService": { + "google.cloud.language.v1.LanguageService": { "retry_codes": { "retry_codes_def": { "idempotent": [ @@ -32,6 +32,11 @@ "retry_codes_name": "idempotent", "retry_params_name": "default" }, + "AnalyzeSyntax": { + "timeout_millis": 30000, + "retry_codes_name": "idempotent", + "retry_params_name": "default" + }, "AnnotateText": { "timeout_millis": 30000, "retry_codes_name": "idempotent", diff --git a/packages/language/system-test/language.js b/packages/language/system-test/language.js index 4e295f8f5d8..4a21bc1c453 100644 --- a/packages/language/system-test/language.js +++ b/packages/language/system-test/language.js @@ -288,6 +288,34 @@ describe('Language', function() { }, validateSentimentVerbose(done)); }); }); + + describe('syntax', function() { + it('should work without creating a document', function(done) { + if (!CONTENT_TYPE) { + language.detectSyntax( + CONTENT, + validateSyntaxSimple(done) + ); + return; + } + + language.detectSyntax( + CONTENT, + { type: CONTENT_TYPE }, + validateSyntaxSimple(done) + ); + }); + + it('should return the correct simplified response', function(done) { + DOC.detectSyntax(validateSyntaxSimple(done)); + }); + + it('should support verbose mode', function(done) { + DOC.detectSyntax({ + verbose: true + }, validateSyntaxVerbose(done)); + }); + }); }); }); }); @@ -331,7 +359,23 @@ describe('Language', function() { assert.deepEqual(annotation.tokens[0], { text: 'Hello', partOfSpeech: 'Other: foreign words, typos, abbreviations', - partOfSpeechTag: 'X' + tag: 'X', + aspect: undefined, + case: undefined, + form: undefined, + gender: undefined, + mood: undefined, + number: undefined, + person: undefined, + proper: undefined, + reciprocity: undefined, + tense: undefined, + voice: undefined, + dependencyEdge: { + description: 'Root', + label: 'ROOT', + headTokenIndex: 0 + } }); assert(is.object(apiResponse)); @@ -464,7 +508,6 @@ describe('Language', function() { assert.ifError(err); assert(is.number(sentiment)); - assert(is.object(apiResponse)); callback(); @@ -480,8 +523,10 @@ describe('Language', function() { assert.ifError(err); assert(is.object(sentiment)); - assert(is.number(sentiment.polarity)); + assert(is.number(sentiment.score)); assert(is.number(sentiment.magnitude)); + assert.strictEqual(sentiment.language, 'en'); + assert.strictEqual(sentiment.sentences.length, 2); assert(is.object(apiResponse)); @@ -491,5 +536,60 @@ describe('Language', function() { } }; } + + function validateSyntaxSimple(callback) { + return function(err, tokens, apiResponse) { + try { + assert.ifError(err); + assert.strictEqual(tokens.length, 17); + assert.deepEqual(tokens[0], { + aspect: undefined, + case: undefined, + form: undefined, + gender: undefined, + mood: undefined, + number: undefined, + partOfSpeech: 'Other: foreign words, typos, abbreviations', + person: undefined, + proper: undefined, + reciprocity: undefined, + tag: 'X', + tense: undefined, + text: 'Hello', + voice: undefined, + dependencyEdge: { + description: 'Root', + headTokenIndex: 0, + label: 'ROOT' + } + }); + + assert(is.object(apiResponse)); + + callback(); + } catch (e) { + callback(e); + } + }; + } + + function validateSyntaxVerbose(callback) { + return function(err, syntax, apiResponse) { + try { + assert.ifError(err); + assert.strictEqual(syntax.sentences.length, 2); + assert(is.object(syntax.sentences[0])); + assert.strictEqual(syntax.tokens.length, 17); + assert(is.object(syntax.tokens[0])); + assert.strictEqual(syntax.language, 'en'); + + assert(is.object(apiResponse)); + + callback(); + } catch (e) { + callback(e); + } + }; + } }); diff --git a/packages/language/test/document.js b/packages/language/test/document.js index 9c74bb5c45a..5ad6b119e21 100644 --- a/packages/language/test/document.js +++ b/packages/language/test/document.js @@ -154,6 +154,93 @@ describe('Document', function() { }); }); + describe('LABEL_DESCRIPTIONS', function() { + var expectedDescriptions = { + UNKNOWN: 'Unknown', + ABBREV: 'Abbreviation modifier', + ACOMP: 'Adjectival complement', + ADVCL: 'Adverbial clause modifier', + ADVMOD: 'Adverbial modifier', + AMOD: 'Adjectival modifier of an NP', + APPOS: 'Appositional modifier of an NP', + ATTR: 'Attribute dependent of a copular verb', + AUX: 'Auxiliary (non-main) verb', + AUXPASS: 'Passive auxiliary', + CC: 'Coordinating conjunction', + CCOMP: 'Clausal complement of a verb or adjective', + CONJ: 'Conjunct', + CSUBJ: 'Clausal subject', + CSUBJPASS: 'Clausal passive subject', + DEP: 'Dependency (unable to determine)', + DET: 'Determiner', + DISCOURSE: 'Discourse', + DOBJ: 'Direct object', + EXPL: 'Expletive', + GOESWITH: ' Goes with (part of a word in a text not well edited)', + IOBJ: 'Indirect object', + MARK: 'Marker (word introducing a subordinate clause)', + MWE: 'Multi-word expression', + MWV: 'Multi-word verbal expression', + NEG: 'Negation modifier', + NN: 'Noun compound modifier', + NPADVMOD: 'Noun phrase used as an adverbial modifier', + NSUBJ: 'Nominal subject', + NSUBJPASS: 'Passive nominal subject', + NUM: 'Numeric modifier of a noun', + NUMBER: 'Element of compound number', + P: 'Punctuation mark', + PARATAXIS: 'Parataxis relation', + PARTMOD: 'Participial modifier', + PCOMP: 'The complement of a preposition is a clause', + POBJ: 'Object of a preposition', + POSS: 'Possession modifier', + POSTNEG: 'Postverbal negative particle', + PRECOMP: 'Predicate complement', + PRECONJ: 'Preconjunt', + PREDET: 'Predeterminer', + PREF: 'Prefix', + PREP: 'Prepositional modifier', + PRONL: 'The relationship between a verb and verbal morpheme', + PRT: 'Particle', + PS: 'Associative or possessive marker', + QUANTMOD: 'Quantifier phrase modifier', + RCMOD: 'Relative clause modifier', + RCMODREL: 'Complementizer in relative clause', + RDROP: 'Ellipsis without a preceding predicate', + REF: 'Referent', + REMNANT: 'Remnant', + REPARANDUM: 'Reparandum', + ROOT: 'Root', + SNUM: 'Suffix specifying a unit of number', + SUFF: 'Suffix', + TMOD: 'Temporal modifier', + TOPIC: 'Topic marker', + VMOD: + 'Clause headed by an infinite form of the verb that modifies a noun', + VOCATIVE: 'Vocative', + XCOMP: 'Open clausal complement', + SUFFIX: 'Name suffix', + TITLE: 'Name title', + ADVPHMOD: 'Adverbial phrase modifier', + AUXCAUS: 'Causative auxiliary', + AUXVV: 'Helper auxiliary', + DTMOD: 'Rentaishi (Prenominal modifier)', + FOREIGN: 'Foreign words', + KW: 'Keyword', + LIST: 'List for chains of comparable items', + NOMC: 'Nominalized clause', + NOMCSUBJ: 'Nominalized clausal subject', + NOMCSUBJPASS: 'Nominalized clausal passive', + NUMC: 'Compound of numeric modifier', + COP: 'Copula', + DISLOCATED: 'Dislocated relation (for fronted/topicalized elements)' + }; + + it('should contain the correct list of label descriptions', function() { + assert.deepEqual(Document.LABEL_DESCRIPTIONS, expectedDescriptions); + }); + }); + describe('PART_OF_SPEECH', function() { var expectedPartOfSpeech = { UNKNOWN: 'Unknown', @@ -560,7 +647,9 @@ describe('Document', function() { describe('success', function() { var apiResponse = { - documentSentiment: {} + documentSentiment: {}, + sentences: [], + language: 'en' }; var originalApiResponse = extend({}, apiResponse); @@ -601,14 +690,154 @@ describe('Document', function() { }); it('should allow verbose mode', function(done) { + var fakeSentiment = {}; + Document.formatSentiment_ = function(sentiment, verbose) { + assert.strictEqual(sentiment, apiResponse.documentSentiment); + assert.strictEqual(verbose, true); + return fakeSentiment; + }; + + var fakeSentences = []; + + Document.formatSentences_ = function(sentences, verbose) { + assert.strictEqual(sentences, apiResponse.sentences); assert.strictEqual(verbose, true); + return fakeSentences; + }; + + var options = { + verbose: true + }; + + document.detectSentiment(options, function(err, sentiment, resp) { + assert.ifError(err); + + assert.strictEqual(sentiment, fakeSentiment); + assert.strictEqual(sentiment.sentences, fakeSentences); + assert.strictEqual(sentiment.language, 'en'); + + assert.deepEqual(resp, apiResponse); + + done(); + }); + }); + }); + }); + + describe('detectSyntax', function() { + it('should make the correct API request', function(done) { + document.api.Language = { + analyzeSyntax: function(reqOpts) { + assert.strictEqual(reqOpts.document, document.document); + assert.strictEqual(reqOpts.encodingType, document.encodingType); done(); + } + }; + + document.encodingType = 'encoding-type'; + document.detectSyntax(assert.ifError); + }); + + describe('error', function() { + var apiResponse = {}; + var error = new Error('Error.'); + + beforeEach(function() { + document.api.Language = { + analyzeSyntax: function(reqOpts, callback) { + callback(error, apiResponse); + } }; + }); - document.detectSentiment({ + it('should exec callback with error and API response', function(done) { + document.detectSyntax(function(err, syntax, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(syntax, null); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + + describe('success', function() { + var apiResponse = { + sentences: [{}], + tokens: [{}], + language: 'en' + }; + + var originalApiResponse = extend({}, apiResponse); + + beforeEach(function() { + Document.formatTokens_ = util.noop; + Document.formatSentences_ = util.noop; + + document.api.Language = { + analyzeSyntax: function(reqOpts, callback) { + callback(null, apiResponse); + } + }; + }); + + it('should format the tokens', function(done) { + var formattedTokens = [{}]; + + Document.formatTokens_ = function(tokens, verbose) { + assert.strictEqual(tokens, apiResponse.tokens); + assert.strictEqual(verbose, false); + return formattedTokens; + }; + + document.detectSyntax(function(err, syntax) { + assert.ifError(err); + assert.strictEqual(syntax, formattedTokens); + done(); + }); + }); + + it('should clone the response object', function(done) { + document.detectSyntax(function(err, syntax, apiResponse_) { + assert.ifError(err); + assert.notStrictEqual(apiResponse_, apiResponse); + assert.deepEqual(apiResponse_, originalApiResponse); + done(); + }); + }); + + it('should allow verbose mode', function(done) { + var fakeTokens = []; + + Document.formatTokens_ = function(tokens, verbose) { + assert.strictEqual(tokens, apiResponse.tokens); + assert.strictEqual(verbose, true); + return fakeTokens; + }; + + var fakeSentences = []; + + Document.formatSentences_ = function(sentences, verbose) { + assert.strictEqual(sentences, apiResponse.sentences); + assert.strictEqual(verbose, true); + return fakeSentences; + }; + + var options = { verbose: true - }, assert.ifError); + }; + + document.detectSyntax(options, function(err, syntax, resp) { + assert.ifError(err); + + assert.strictEqual(syntax.tokens, fakeTokens); + assert.strictEqual(syntax.sentences, fakeSentences); + assert.strictEqual(syntax.language, 'en'); + + assert.deepEqual(resp, apiResponse); + + done(); + }); }); }); }); @@ -716,7 +945,7 @@ describe('Document', function() { var EXPECTED_FORMATTED_SENTENCES = { default: SENTENCES.map(prop('text')).map(prop('content')), - verbose: SENTENCES.map(prop('text')) + verbose: SENTENCES }; it('should correctly format sentences', function() { @@ -740,17 +969,17 @@ describe('Document', function() { describe('formatSentiment_', function() { var SENTIMENT = { - polarity: -0.5, + score: -0.5, magnitude: 0.5 }; var VERBOSE = false; var EXPECTED_FORMATTED_SENTIMENT = { - default: SENTIMENT.polarity * 100, + default: SENTIMENT.score * 100, verbose: { - polarity: SENTIMENT.polarity * 100, - magnitude: SENTIMENT.magnitude * 100 + score: SENTIMENT.score * 100, + magnitude: SENTIMENT.magnitude } }; @@ -782,14 +1011,22 @@ describe('Document', function() { content: 'Text content' }, partOfSpeech: { - tag: 'PART_OF_SPEECH_TAG' + tag: 'PART_OF_SPEECH_TAG', + fakePart: 'UNKNOWN' }, - property: 'value' + property: 'value', + dependencyEdge: { + label: 'FAKE_LABEL' + } } ]; var VERBOSE = false; + var LABEL_DESCRIPTIONS = { + FAKE_LABEL: 'fake label description' + }; + var PART_OF_SPEECH = { PART_OF_SPEECH_TAG: 'part of speech value' }; @@ -799,7 +1036,12 @@ describe('Document', function() { return { text: token.text.content, partOfSpeech: PART_OF_SPEECH.PART_OF_SPEECH_TAG, - partOfSpeechTag: 'PART_OF_SPEECH_TAG' + tag: 'PART_OF_SPEECH_TAG', + fakePart: undefined, + dependencyEdge: { + label: 'FAKE_LABEL', + description: LABEL_DESCRIPTIONS.FAKE_LABEL + } }; }), @@ -808,6 +1050,7 @@ describe('Document', function() { beforeEach(function() { Document.PART_OF_SPEECH = PART_OF_SPEECH; + Document.LABEL_DESCRIPTIONS = LABEL_DESCRIPTIONS; }); it('should correctly format tokens', function() { diff --git a/packages/language/test/index.js b/packages/language/test/index.js index 1e4a5c9baee..37ed64aa6ba 100644 --- a/packages/language/test/index.js +++ b/packages/language/test/index.js @@ -37,10 +37,10 @@ function FakeDocument() { this.calledWith_ = arguments; } -var fakeV1Beta1Override; -function fakeV1Beta1() { - if (fakeV1Beta1Override) { - return fakeV1Beta1Override.apply(null, arguments); +var fakeV1Override; +function fakeV1() { + if (fakeV1Override) { + return fakeV1Override.apply(null, arguments); } return { @@ -60,12 +60,12 @@ describe('Language', function() { util: fakeUtil }, './document.js': FakeDocument, - './v1beta1': fakeV1Beta1 + './v1': fakeV1 }); }); beforeEach(function() { - fakeV1Beta1Override = null; + fakeV1Override = null; language = new Language(OPTIONS); }); @@ -102,7 +102,7 @@ describe('Language', function() { it('should create a gax api client', function() { var expectedLanguageService = {}; - fakeV1Beta1Override = function(options) { + fakeV1Override = function(options) { assert.strictEqual(options, OPTIONS); return { @@ -262,6 +262,54 @@ describe('Language', function() { }); }); + describe('detectSyntax', function() { + var CONTENT = '...'; + var OPTIONS = { + property: 'value' + }; + + var EXPECTED_OPTIONS = { + withCustomOptions: extend({}, OPTIONS, { + content: CONTENT + }), + + withoutCustomOptions: extend({}, { + content: CONTENT + }) + }; + + it('should call detectSyntax on a Document', function(done) { + language.document = function(options) { + assert.deepEqual(options, EXPECTED_OPTIONS.withCustomOptions); + + return { + detectSyntax: function(options, callback) { + assert.deepEqual(options, EXPECTED_OPTIONS.withCustomOptions); + callback(); // done() + } + }; + }; + + language.detectSyntax(CONTENT, OPTIONS, done); + }); + + it('should not require options', function(done) { + language.document = function(options) { + assert.deepEqual(options, EXPECTED_OPTIONS.withoutCustomOptions); + + return { + detectSyntax: function(options, callback) { + assert.deepEqual(options, EXPECTED_OPTIONS.withoutCustomOptions); + callback(); // done() + } + }; + }; + + language.detectSyntax(CONTENT, done); + }); + }); + + describe('document', function() { var CONFIG = {};