<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */;

var utils = require('../internal/convert');
var v = require('../internal/validate');

/**
 * A Find Place request takes a text input, and returns a place.
 * The text input can be any kind of Places data, for example,
 * a name, address, or phone number.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.findPlaces
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.inputtype
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.findPlaces = {
  url: 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json',
  validator: v.compose([
    v.object({
      input: v.string,
      inputtype: v.oneOf(['textquery', 'phonenumber']),
      language: v.optional(v.string),
      fields: v.optional(utils.arrayOf(v.oneOf([
        'alt_id', 'formatted_address', 'geometry', 'icon', 'id', 'name',
        'permanently_closed', 'photos', 'place_id', 'scope', 'types',
        'vicinity', 'opening_hours', 'price_level', 'rating'
      ]), ',')),
      locationbias: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    }),
    function(query) {
      var latLng = /^([-+]?\d{1,2}([.]\d+)?),*([-+]?\d{1,3}([.]\d+)?)$/;
      var parts = ['ipbias|point:', '|circle:([1-9]d*)+@', '|rectangle:', '\\|'];
      var re = new RegExp(parts.join(latLng.source), 'g');
      if (query.locationbias && !re.test(query.locationbias)) {
        throw new v.InvalidValueError('invalid locationbias');
      }
      return query;
    }
  ])
};

/**
 * Makes a places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.places
 * @function
 * @param {Object} query
 * @param {string} query.query
 * @param {string} [query.language]
 * @param {LatLng} [query.location]
 * @param {number} [query.radius]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {string} [query.region]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.places = {
  url: 'https://maps.googleapis.com/maps/api/place/textsearch/json',
  validator: v.object({
    query: v.optional(v.string),
    language: v.optional(v.string),
    location: v.optional(utils.latLng),
    radius: v.optional(v.number),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    pagetoken: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number),
    region: v.optional(v.string)
  })
};

/**
 * Makes a nearby places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesNearby
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.rankby] Either 'prominence' or 'distance'
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesNearby = {
  url: 'https://maps.googleapis.com/maps/api/place/nearbysearch/json',
  validator: v.compose([
    v.mutuallyExclusivePropertiesRequired(['location', 'pagetoken']),
    v.object({
      location: v.optional(utils.latLng),
      language: v.optional(v.string),
      radius: v.optional(v.number),
      keyword: v.optional(v.string),
      minprice: v.optional(v.number),
      maxprice: v.optional(v.number),
      name: v.optional(v.string),
      opennow: v.optional(v.boolean),
      rankby: v.optional(v.oneOf(['prominence', 'distance'])),
      type: v.optional(v.string),
      pagetoken: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    })
  ])
};

/**
 * Makes a places radar search request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesRadar
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {number} query.radius
 * @param {string} [query.language]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesRadar = {
  url: 'https://maps.googleapis.com/maps/api/place/radarsearch/json',
  validator: v.object({
    location: utils.latLng,
    radius: v.number,
    language: v.optional(v.string),
    keyword: v.optional(v.string),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    name: v.optional(v.string),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place detail request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.place
 * @function
 * @param {Object} query
 * @param {string} query.placeid
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.place = {
  url: 'https://maps.googleapis.com/maps/api/place/details/json',
  validator: v.object({
    placeid: v.string,
    language: v.optional(v.string),
    fields: v.optional(utils.arrayOf(v.oneOf([
      'address_component', 'adr_address', 'formatted_address',
      'geometry', 'icon', 'id', 'name', 'permanently_closed', 'photo',
      'place_id', 'scope', 'type', 'url', 'utc_offset', 'vicinity',
      'formatted_phone_number', 'international_phone_number', 'opening_hours',
      'website', 'price_level', 'rating', 'review',
    ]), ',')),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place photos request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesPhoto
 * @function
 * @param {Object} query
 * @param {string} query.photoreference
 * @param {number} [query.maxwidth]
 * @param {number} [query.maxheight]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesPhoto = {
  url: 'https://maps.googleapis.com/maps/api/place/photo',
  validator: v.object({
    photoreference: v.string,
    maxwidth: v.optional(v.number),
    maxheight: v.optional(v.number),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a places autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.sessiontoken
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.types]
 * @param {Array<string>} [query.components]
 * @param {boolean} [query.strictbounds]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/autocomplete/json',
  validator: v.object({
    input: v.string,
    sessiontoken: v.string,
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    types: v.optional(v.oneOf(['geocode', 'address', 'establishment', '(regions)', '(cities)'])),
    components: v.optional(utils.pipedKeyValues),
    strictbounds: v.optional(v.boolean),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a places query autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesQueryAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesQueryAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json',
  validator: v.object({
    input: v.string,
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};
<commit_msg>Remove alt_id<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */;

var utils = require('../internal/convert');
var v = require('../internal/validate');

/**
 * A Find Place request takes a text input, and returns a place.
 * The text input can be any kind of Places data, for example,
 * a name, address, or phone number.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.findPlaces
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.inputtype
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.findPlaces = {
  url: 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json',
  validator: v.compose([
    v.object({
      input: v.string,
      inputtype: v.oneOf(['textquery', 'phonenumber']),
      language: v.optional(v.string),
      fields: v.optional(utils.arrayOf(v.oneOf([
        'formatted_address', 'geometry', 'icon', 'id', 'name',
        'permanently_closed', 'photos', 'place_id', 'scope', 'types',
        'vicinity', 'opening_hours', 'price_level', 'rating'
      ]), ',')),
      locationbias: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    }),
    function(query) {
      var latLng = /^([-+]?\d{1,2}([.]\d+)?),*([-+]?\d{1,3}([.]\d+)?)$/;
      var parts = ['ipbias|point:', '|circle:([1-9]d*)+@', '|rectangle:', '\\|'];
      var re = new RegExp(parts.join(latLng.source), 'g');
      if (query.locationbias && !re.test(query.locationbias)) {
        throw new v.InvalidValueError('invalid locationbias');
      }
      return query;
    }
  ])
};

/**
 * Makes a places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.places
 * @function
 * @param {Object} query
 * @param {string} query.query
 * @param {string} [query.language]
 * @param {LatLng} [query.location]
 * @param {number} [query.radius]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {string} [query.region]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.places = {
  url: 'https://maps.googleapis.com/maps/api/place/textsearch/json',
  validator: v.object({
    query: v.optional(v.string),
    language: v.optional(v.string),
    location: v.optional(utils.latLng),
    radius: v.optional(v.number),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    pagetoken: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number),
    region: v.optional(v.string)
  })
};

/**
 * Makes a nearby places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesNearby
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.rankby] Either 'prominence' or 'distance'
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesNearby = {
  url: 'https://maps.googleapis.com/maps/api/place/nearbysearch/json',
  validator: v.compose([
    v.mutuallyExclusivePropertiesRequired(['location', 'pagetoken']),
    v.object({
      location: v.optional(utils.latLng),
      language: v.optional(v.string),
      radius: v.optional(v.number),
      keyword: v.optional(v.string),
      minprice: v.optional(v.number),
      maxprice: v.optional(v.number),
      name: v.optional(v.string),
      opennow: v.optional(v.boolean),
      rankby: v.optional(v.oneOf(['prominence', 'distance'])),
      type: v.optional(v.string),
      pagetoken: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    })
  ])
};

/**
 * Makes a places radar search request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesRadar
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {number} query.radius
 * @param {string} [query.language]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesRadar = {
  url: 'https://maps.googleapis.com/maps/api/place/radarsearch/json',
  validator: v.object({
    location: utils.latLng,
    radius: v.number,
    language: v.optional(v.string),
    keyword: v.optional(v.string),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    name: v.optional(v.string),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place detail request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.place
 * @function
 * @param {Object} query
 * @param {string} query.placeid
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.place = {
  url: 'https://maps.googleapis.com/maps/api/place/details/json',
  validator: v.object({
    placeid: v.string,
    language: v.optional(v.string),
    fields: v.optional(utils.arrayOf(v.oneOf([
      'address_component', 'adr_address', 'alt_id', 'formatted_address',
      'geometry', 'icon', 'id', 'name', 'permanently_closed', 'photo',
      'place_id', 'scope', 'type', 'url', 'utc_offset', 'vicinity',
      'formatted_phone_number', 'international_phone_number', 'opening_hours',
      'website', 'price_level', 'rating', 'review',
    ]), ',')),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place photos request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesPhoto
 * @function
 * @param {Object} query
 * @param {string} query.photoreference
 * @param {number} [query.maxwidth]
 * @param {number} [query.maxheight]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesPhoto = {
  url: 'https://maps.googleapis.com/maps/api/place/photo',
  validator: v.object({
    photoreference: v.string,
    maxwidth: v.optional(v.number),
    maxheight: v.optional(v.number),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a places autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.sessiontoken
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.types]
 * @param {Array<string>} [query.components]
 * @param {boolean} [query.strictbounds]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/autocomplete/json',
  validator: v.object({
    input: v.string,
    sessiontoken: v.string,
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    types: v.optional(v.oneOf(['geocode', 'address', 'establishment', '(regions)', '(cities)'])),
    components: v.optional(utils.pipedKeyValues),
    strictbounds: v.optional(v.boolean),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a places query autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesQueryAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesQueryAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json',
  validator: v.object({
    input: v.string,
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};
<|endoftext|>


<commit_before>describe("Ti.Database tests", {
	testModuleMethods: function() {
		valueOf(Ti.Database).shouldNotBeNull();
		valueOf(Ti.Database).shouldBeObject();
		valueOf(Ti.Database.open).shouldBeFunction();
		valueOf(Ti.Database.install).shouldBeFunction();
	},
	testDatabaseMethods : function() {
		var db = Ti.Database.open("Test");
		try {
			valueOf(db).shouldNotBeNull();
			valueOf(db).shouldBeObject();
			valueOf(db.close).shouldBeFunction();
			valueOf(db.execute).shouldBeFunction();
			valueOf(db.getLastInsertRowId).shouldBeFunction();
			valueOf(db.getName).shouldBeFunction();
			valueOf(db.getRowsAffected).shouldBeFunction();
			valueOf(db.remove).shouldBeFunction();

			// Properties
			valueOf(db.lastInsertRowId).shouldBeNumber();
			valueOf(db.name).shouldBeString();
			valueOf(db.name).shouldBe("Test");
			valueOf(db.rowsAffected).shouldBeNumber();
		} finally {
			db.close();
		}
	},
	// https://appcelerator.lighthouseapp.com/projects/32238-titanium-mobile/tickets/2147-android-pragma-and-non-select-statements-return-null-from-tidatabasedbexecute-instead-of-resultset
	testDatabaseLH2147 : function() {
		var db = Ti.Database.open("Test");
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists Test");
			valueOf(rs).shouldBeNull();

			rs = db.execute("create table if not exists Test(row text)");
			valueOf(rs).shouldBeNull();

			rs = db.execute("pragma table_info(Test)");
			valueOf(rs).shouldNotBeNull();
			valueOf(rs.fieldCount).shouldBeGreaterThan(0);
			rs.close();

			rs = db.execute("select * from Test");
			valueOf(rs).shouldNotBeNull();
			valueOf(rs.getFieldCount()).shouldBe(1);
			valueOf(rs.rowCount).shouldBe(0);
			rs.close();
		} finally {
			db.close();
			db.remove();
		}

		var f = Ti.Filesystem.getFile("file:///data/data/org.appcelerator.titanium.testharness/databases/Test");
		valueOf(f.exists()).shouldBeFalse();
	},
	testDatabaseInsert : function() {
		var db = Ti.Database.open("Test");
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists Test");
			valueOf(rs).shouldBeNull();

			rs = db.execute("create table if not exists Test(row text)");
			valueOf(rs).shouldBeNull();

			db.execute("insert into Test(row) values(?)", "My TestRow");

			rs = db.execute("select * from Test");
			valueOf(rs).shouldNotBeNull();
			valueOf(rs.isValidRow()).shouldBe(true);
			valueOf(rs.getFieldCount()).shouldBe(1);
			valueOf(rs.rowCount).shouldBe(1);
			valueOf(rs.getField(0)).shouldBe("My TestRow");
			rs.close();
		} finally {
			db.close();
			db.remove();
		}

		var f = Ti.Filesystem.getFile("file:///data/data/org.appcelerator.titanium.testharness/databases/Test");
		valueOf(f.exists()).shouldBeFalse();
	},
	testDatabaseCount : function() {
		var testRowCount = 1500;
		var db = Ti.Database.open('Test');
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists data");
			valueOf(rs).shouldBeNull();

			db.execute('CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY, val TEXT)');
			for (var i = 1; i <= testRowCount; i++) {
			    db.execute('INSERT INTO data (val) VALUES(?)','our value:' + i);
			}

		    rs = db.execute("SELECT * FROM data");
		    var rowCount = rs.rowCount;
		    var realCount = 0;
		    while (rs.isValidRow()) {
		        realCount += 1;
		        rs.next();
		    }

		    valueOf(realCount).shouldBe(testRowCount);
		    valueOf(rowCount).shouldBe(testRowCount);
		    valueOf(rowCount).shouldBe(realCount);
		} finally {
			db.close();
			db.remove();
		}
	}
});<commit_msg>[#364] Drillbit test for begin/rollback/commit and savepoints in database.<commit_after>describe("Ti.Database tests", {
	testModuleMethods: function() {
		valueOf(Ti.Database).shouldNotBeNull();
		valueOf(Ti.Database).shouldBeObject();
		valueOf(Ti.Database.open).shouldBeFunction();
		valueOf(Ti.Database.install).shouldBeFunction();
	},
	testDatabaseMethods : function() {
		var db = Ti.Database.open("Test");
		try {
			valueOf(db).shouldNotBeNull();
			valueOf(db).shouldBeObject();
			valueOf(db.close).shouldBeFunction();
			valueOf(db.execute).shouldBeFunction();
			valueOf(db.getLastInsertRowId).shouldBeFunction();
			valueOf(db.getName).shouldBeFunction();
			valueOf(db.getRowsAffected).shouldBeFunction();
			valueOf(db.remove).shouldBeFunction();

			// Properties
			valueOf(db.lastInsertRowId).shouldBeNumber();
			valueOf(db.name).shouldBeString();
			valueOf(db.name).shouldBe("Test");
			valueOf(db.rowsAffected).shouldBeNumber();
		} finally {
			db.close();
		}
	},
	// https://appcelerator.lighthouseapp.com/projects/32238-titanium-mobile/tickets/2147-android-pragma-and-non-select-statements-return-null-from-tidatabasedbexecute-instead-of-resultset
	testDatabaseLH2147 : function() {
		var db = Ti.Database.open("Test");
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists Test");
			valueOf(rs).shouldBeNull();

			rs = db.execute("create table if not exists Test(row text)");
			valueOf(rs).shouldBeNull();

			rs = db.execute("pragma table_info(Test)");
			valueOf(rs).shouldNotBeNull();
			valueOf(rs.fieldCount).shouldBeGreaterThan(0);
			rs.close();

			rs = db.execute("select * from Test");
			valueOf(rs).shouldNotBeNull();
			valueOf(rs.getFieldCount()).shouldBe(1);
			valueOf(rs.rowCount).shouldBe(0);
			rs.close();
		} finally {
			db.close();
			db.remove();
		}

		var f = Ti.Filesystem.getFile("file:///data/data/org.appcelerator.titanium.testharness/databases/Test");
		valueOf(f.exists()).shouldBeFalse();
	},
	testDatabaseInsert : function() {
		var db = Ti.Database.open("Test");
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists Test");
			valueOf(rs).shouldBeNull();

			rs = db.execute("create table if not exists Test(row text)");
			valueOf(rs).shouldBeNull();

			db.execute("insert into Test(row) values(?)", "My TestRow");

			rs = db.execute("select * from Test");
			valueOf(rs).shouldNotBeNull();
			valueOf(rs.isValidRow()).shouldBe(true);
			valueOf(rs.getFieldCount()).shouldBe(1);
			valueOf(rs.rowCount).shouldBe(1);
			valueOf(rs.getField(0)).shouldBe("My TestRow");
			rs.close();
		} finally {
			db.close();
			db.remove();
		}

		var f = Ti.Filesystem.getFile("file:///data/data/org.appcelerator.titanium.testharness/databases/Test");
		valueOf(f.exists()).shouldBeFalse();
	},
	testDatabaseCount : function() {
		var testRowCount = 1500;
		var db = Ti.Database.open('Test');
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists data");
			valueOf(rs).shouldBeNull();

			db.execute('CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY, val TEXT)');
			for (var i = 1; i <= testRowCount; i++) {
			    db.execute('INSERT INTO data (val) VALUES(?)','our value:' + i);
			}

		    rs = db.execute("SELECT * FROM data");
		    var rowCount = rs.rowCount;
		    var realCount = 0;
		    while (rs.isValidRow()) {
		        realCount += 1;
		        rs.next();
		    }

		    valueOf(realCount).shouldBe(testRowCount);
		    valueOf(rowCount).shouldBe(testRowCount);
		    valueOf(rowCount).shouldBe(realCount);
		} finally {
			db.close();
			db.remove();
		}
	},
	testDatabaseRollback : function () {
		var db = Ti.Database.open('Test');
		var testRowCount = 30;
		try {
			valueOf(db).shouldNotBeNull();

			var rs = db.execute("drop table if exists data");
			valueOf(rs).shouldBeNull();

			db.execute('BEGIN DEFERRED TRANSACTION');
			db.execute('CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY, val TEXT)');
			db.execute('SAVEPOINT FOO');
			for (var i = 1; i <= testRowCount; i++) {
			    db.execute('INSERT INTO data (val) VALUES(?)','our value:' + i);
			}
			db.execute('ROLLBACK TRANSACTION TO SAVEPOINT FOO');
			db.execute('COMMIT TRANSACTION');

			rs = db.execute("SELECT * FROM data");
			valueOf(rs.rowCount).shouldBe(0);

			db.execute('BEGIN TRANSACTION');
			db.execute('drop table if exists data');
			db.execute('ROLLBACK TRANSACTION');

			rs = db.execute("SELECT * FROM data");
			valueOf(rs).shouldNotBeNull();

		} finally {
			db.close();
			db.remove();
		}
	}
});<|endoftext|>


<commit_before>import assert from "assert"
import tmp from "tmp"
import path from "path"
import fs from "fs"
import concat from "concat-stream"

import LogFileGenerator from "../mock/LogFileGenerator.js"
import LiveIndex from "../lib/LiveIndex.js"

tmp.setGracefulCleanup()

const fooFixturePath = path.resolve(__dirname, "..", "fixtures", "foo.txt")
const fooFixtureData = fs.readFileSync(fooFixturePath)
const barFixturePath = path.resolve(__dirname, "..", "fixtures", "bar.txt")
const barFixtureData = fs.readFileSync(barFixturePath)

describe("LiveIndex", () => {
  let index, logGenerator, dir, logPath

  const simpleIndexer = (data, addIndex) => {
    let pos = 0
    data.toString().split(/\n+/).forEach(s => {
      const m = s.match(/[^:]+/);
      if (m) { addIndex(m[0], pos) }
      pos += s.length + 1
    })
  }

  beforeEach(done => {
    logGenerator = new LogFileGenerator()
    dir = tmp.dirSync({ unsafeCleanup: true }).name
    logPath = path.join(dir, "foo.log")
    logGenerator.createLog(logPath)
    logGenerator.on("created", () => {
      index = new LiveIndex({ pathToWatch: logPath })
      return done()
    })
  })

  describe("fileAndPositionForIdentifier()", () => {
    beforeEach(() => {
      index.setIndexer(simpleIndexer)
      index.watch()
    })

    it("should return the correct file and position for the id", done => {
      index.once("insert", id => {
        assert.strictEqual(id, logGenerator.ids[0])
        const result = index.fileAndPositionForIdentifier(id)
        assert.strictEqual(result.file, logPath)
        assert.strictEqual(result.position, 0)
        return done()
      })
      logGenerator.writeLog()
    })

    it("should update the file name when the underlying file is renamed", done => {
      logGenerator.on("flushed", () => {
        const newPath = path.join(dir, "renamed.txt")
        logGenerator.renameFile(newPath, () => {
          setTimeout(() => {
            const result = index.fileAndPositionForIdentifier(logGenerator.ids[0])
            assert.strictEqual(result.file, newPath)
            return done()
          }, 40)
        })
      })
      logGenerator.writeLog()
    })
  })

  describe("indexer", () => {
    it("processedTo callback should cause unprocessed buffer to be appended in next chunk", done => {
      let callCount = 0
      index.setIndexer((data, addIndex, processedTo) => {
        callCount += 1
        if (callCount === 1) {
          assert(processedTo)
          processedTo(7)
        }
        else if (callCount === 2) {
          const expected = logGenerator.lines[0].slice(7) + logGenerator.lines[1]
          assert.strictEqual(data.toString(), expected)
          return done()
        }
      })
      index.watch()
      logGenerator.writeLog()
    })
  })

  describe("setIndexStorageObject()", () => {
    let mockStore

    beforeEach(() => {
      mockStore = new MockIndexStorage()
      index = new LiveIndex()
      index.setIndexStorageObject(mockStore)
    })

    it("should update the object that is used when calling .insert()", () => {
      index.insert("foo", "file", 5)
      mockStore.assertSetCalledWith("foo", {file: undefined, position: 5})
    })

    it("should update the object that is used when calling fileAndPositionForIdentifier()", () => {
      index.fileAndPositionForIdentifier("foo")
      mockStore.assertGetCalledWith("foo")
    })
  })

  describe("addStaticDataFile()", () => {
    it("should add a file to the index", done => {
      index.setIndexer(simpleIndexer)
      index.watch()

      logGenerator.on("flushed", () => {

        index.addStaticDataFile(barFixturePath, err => {
          assert.ifError(err)
          const expected = {file: barFixturePath, position: 20}
          assert.deepEqual(index.fileAndPositionForIdentifier("EtBQPcgqTHA"), expected)
          return done()
        })
      })
      logGenerator.writeLog()
    })
  })

  describe("readStreamFromIndex()", () => {
    it("should return undefined if index does not exist", () => {
      assert.strictEqual(index.readStreamFromIndex("nonexistent"), undefined)
    })

    it("should return a Readable stream beginning at the offset specified by the index", done => {
      index.setIndexer(simpleIndexer)
      index.addStaticDataFile(barFixturePath, err => added(err, barFixturePath))
      index.addStaticDataFile(fooFixturePath, err => added(err, fooFixturePath))

      let seen = []
      function added (err, path) {
        assert.ifError(err)

        seen.push(path)
        if (seen.length !== 2) {
          return
        }

        index.readStreamFromIndex("6fVmv625zfs").pipe(concat(result => {
          assert(result.equals(fooFixtureData.slice(40)))
          return done()
        }))
      }
    })
  })
})

class MockIndexStorage {
  constructor () {
    this._getCalledWith = null
    this._setCalledWith = null
  }

  get (id) {
    this._getCalledWith = Array.from(arguments)
  }

  set (id, val) {
    this._setCalledWith = Array.from(arguments)
  }

  assertGetCalledWith () {
    assert.deepEqual(this._getCalledWith, Array.from(arguments))
  }

  assertSetCalledWith () {
    assert.deepEqual(this._setCalledWith, Array.from(arguments))
  }
}
<commit_msg>Refactor readStreamFromIndex() tests<commit_after>import assert from "assert"
import tmp from "tmp"
import path from "path"
import fs from "fs"
import concat from "concat-stream"

import LogFileGenerator from "../mock/LogFileGenerator.js"
import LiveIndex from "../lib/LiveIndex.js"

tmp.setGracefulCleanup()

const fooFixturePath = path.resolve(__dirname, "..", "fixtures", "foo.txt")
const fooFixtureData = fs.readFileSync(fooFixturePath)
const barFixturePath = path.resolve(__dirname, "..", "fixtures", "bar.txt")
const barFixtureData = fs.readFileSync(barFixturePath)

describe("LiveIndex", () => {
  let index, logGenerator, dir, logPath

  const simpleIndexer = (data, addIndex) => {
    let pos = 0
    data.toString().split(/\n+/).forEach(s => {
      const m = s.match(/[^:]+/);
      if (m) { addIndex(m[0], pos) }
      pos += s.length + 1
    })
  }

  beforeEach(done => {
    logGenerator = new LogFileGenerator()
    dir = tmp.dirSync({ unsafeCleanup: true }).name
    logPath = path.join(dir, "foo.log")
    logGenerator.createLog(logPath)
    logGenerator.on("created", () => {
      index = new LiveIndex({ pathToWatch: logPath })
      return done()
    })
  })

  describe("fileAndPositionForIdentifier()", () => {
    beforeEach(() => {
      index.setIndexer(simpleIndexer)
      index.watch()
    })

    it("should return the correct file and position for the id", done => {
      index.once("insert", id => {
        assert.strictEqual(id, logGenerator.ids[0])
        const result = index.fileAndPositionForIdentifier(id)
        assert.strictEqual(result.file, logPath)
        assert.strictEqual(result.position, 0)
        return done()
      })
      logGenerator.writeLog()
    })

    it("should update the file name when the underlying file is renamed", done => {
      logGenerator.on("flushed", () => {
        const newPath = path.join(dir, "renamed.txt")
        logGenerator.renameFile(newPath, () => {
          setTimeout(() => {
            const result = index.fileAndPositionForIdentifier(logGenerator.ids[0])
            assert.strictEqual(result.file, newPath)
            return done()
          }, 40)
        })
      })
      logGenerator.writeLog()
    })
  })

  describe("indexer", () => {
    it("processedTo callback should cause unprocessed buffer to be appended in next chunk", done => {
      let callCount = 0
      index.setIndexer((data, addIndex, processedTo) => {
        callCount += 1
        if (callCount === 1) {
          assert(processedTo)
          processedTo(7)
        }
        else if (callCount === 2) {
          const expected = logGenerator.lines[0].slice(7) + logGenerator.lines[1]
          assert.strictEqual(data.toString(), expected)
          return done()
        }
      })
      index.watch()
      logGenerator.writeLog()
    })
  })

  describe("setIndexStorageObject()", () => {
    let mockStore

    beforeEach(() => {
      mockStore = new MockIndexStorage()
      index = new LiveIndex()
      index.setIndexStorageObject(mockStore)
    })

    it("should update the object that is used when calling .insert()", () => {
      index.insert("foo", "file", 5)
      mockStore.assertSetCalledWith("foo", {file: undefined, position: 5})
    })

    it("should update the object that is used when calling fileAndPositionForIdentifier()", () => {
      index.fileAndPositionForIdentifier("foo")
      mockStore.assertGetCalledWith("foo")
    })
  })

  describe("addStaticDataFile()", () => {
    it("should add a file to the index", done => {
      index.setIndexer(simpleIndexer)
      index.watch()

      logGenerator.on("flushed", () => {

        index.addStaticDataFile(barFixturePath, err => {
          assert.ifError(err)
          const expected = {file: barFixturePath, position: 20}
          assert.deepEqual(index.fileAndPositionForIdentifier("EtBQPcgqTHA"), expected)
          return done()
        })
      })
      logGenerator.writeLog()
    })
  })

  describe("readStreamFromIndex()", () => {
    beforeEach(done => {
      index.setIndexer(simpleIndexer)
      index.addStaticDataFile(barFixturePath, err => added(err, barFixturePath))
      index.addStaticDataFile(fooFixturePath, err => added(err, fooFixturePath))

      let seen = []
      function added (err, path) {
        assert.ifError(err)

        seen.push(path)
        if (seen.length !== 2) {
          return done()
        }
      }
    })

    it("should return undefined if index does not exist", () => {
      assert.strictEqual(index.readStreamFromIndex("nonexistent"), undefined)
    })

    it("should return a Readable stream beginning at the offset specified by the index", done => {
      index.readStreamFromIndex("6fVmv625zfs").pipe(concat(result => {
        assert(result.equals(fooFixtureData.slice(40)))
        return done()
      }))
    })
  })
})

class MockIndexStorage {
  constructor () {
    this._getCalledWith = null
    this._setCalledWith = null
  }

  get (id) {
    this._getCalledWith = Array.from(arguments)
  }

  set (id, val) {
    this._setCalledWith = Array.from(arguments)
  }

  assertGetCalledWith () {
    assert.deepEqual(this._getCalledWith, Array.from(arguments))
  }

  assertSetCalledWith () {
    assert.deepEqual(this._setCalledWith, Array.from(arguments))
  }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */;

var utils = require('../internal/convert');
var v = require('../internal/validate');

/**
 * A Find Place request takes a text input, and returns a place.
 * The text input can be any kind of Places data, for example,
 * a name, address, or phone number.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.findPlace
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.inputtype
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.findPlace = {
  url: 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json',
  validator: v.compose([
    v.object({
      input: v.string,
      inputtype: v.oneOf(['textquery', 'phonenumber']),
      language: v.optional(v.string),
      fields: v.optional(utils.arrayOf(v.oneOf([
        'formatted_address', 'geometry', 'icon', 'id', 'name',
        'permanently_closed', 'photos', 'place_id', 'scope', 'types',
        'vicinity', 'opening_hours', 'price_level', 'rating'
      ]), ',')),
      locationbias: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    }),
    function(query) {
      if (!query.locationbias || query.locationbias == 'ipbias') {
        return query;
      }
      var isLatLng = function(latLng) {
        latLng = latLng.split(',');
        return latLng.length == 2 && !isNaN(latLng[0]) && !isNaN(latLng[1]);
      };
      var parts = query.locationbias.split(':');
      switch (parts[0]) {
        case 'point':
          if (isLatLng(parts[parts.length - 1])) {
            return query;
          }
          break;
        case 'circle':
          parts = parts[parts.length - 1].split('@');
          if (!isNaN(parts[0]) && isLatLng(parts[parts.length - 1])) {
            return query;
          }
          break;
        case 'rectangle':
          parts = parts[parts.length - 1].split('|');
          if (parts.length == 2 && isLatLng(parts[0]) && isLatLng(parts[1])) {
            return query;
          }
          break;
      }
      throw new v.InvalidValueError('invalid locationbias');
    }
  ])
};

/**
 * Makes a places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.places
 * @function
 * @param {Object} query
 * @param {string} query.query
 * @param {string} [query.language]
 * @param {LatLng} [query.location]
 * @param {number} [query.radius]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {string} [query.region]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.places = {
  url: 'https://maps.googleapis.com/maps/api/place/textsearch/json',
  validator: v.object({
    query: v.optional(v.string),
    language: v.optional(v.string),
    location: v.optional(utils.latLng),
    radius: v.optional(v.number),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    pagetoken: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number),
    region: v.optional(v.string)
  })
};

/**
 * Makes a nearby places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesNearby
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.rankby] Either 'prominence' or 'distance'
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesNearby = {
  url: 'https://maps.googleapis.com/maps/api/place/nearbysearch/json',
  validator: v.compose([
    v.mutuallyExclusivePropertiesRequired(['location', 'pagetoken']),
    v.object({
      location: v.optional(utils.latLng),
      language: v.optional(v.string),
      radius: v.optional(v.number),
      keyword: v.optional(v.string),
      minprice: v.optional(v.number),
      maxprice: v.optional(v.number),
      name: v.optional(v.string),
      opennow: v.optional(v.boolean),
      rankby: v.optional(v.oneOf(['prominence', 'distance'])),
      type: v.optional(v.string),
      pagetoken: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    })
  ])
};

/**
 * Makes a places radar search request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesRadar
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {number} query.radius
 * @param {string} [query.language]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesRadar = {
  url: 'https://maps.googleapis.com/maps/api/place/radarsearch/json',
  validator: v.object({
    location: utils.latLng,
    radius: v.number,
    language: v.optional(v.string),
    keyword: v.optional(v.string),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    name: v.optional(v.string),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place detail request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.place
 * @function
 * @param {Object} query
 * @param {string} query.placeid
 * @param {string} query.sessiontoken Unique string identifying a single user's session. For convenience use require('@google/maps').util.placesAutoCompleteSessionToken()
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.place = {
  url: 'https://maps.googleapis.com/maps/api/place/details/json',
  validator: v.object({
    placeid: v.string,
    sessiontoken: v.optional(v.string),
    language: v.optional(v.string),
    fields: v.optional(utils.arrayOf(v.oneOf([
      'address_component', 'adr_address', 'alt_id', 'formatted_address',
      'geometry', 'icon', 'id', 'name', 'permanently_closed', 'photo',
      'place_id', 'scope', 'type', 'url', 'utc_offset', 'vicinity',
      'formatted_phone_number', 'international_phone_number', 'opening_hours',
      'website', 'price_level', 'rating', 'review', 'user_ratings_total', 'plus_code'
    ]), ',')),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place photos request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesPhoto
 * @function
 * @param {Object} query
 * @param {string} query.photoreference
 * @param {number} [query.maxwidth]
 * @param {number} [query.maxheight]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesPhoto = {
  url: 'https://maps.googleapis.com/maps/api/place/photo',
  validator: v.compose([
    v.atLeastOneOfProperties(['maxwidth', 'maxheight']),
    v.object({
      photoreference: v.string,
      maxwidth: v.optional(v.number),
      maxheight: v.optional(v.number),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    })
  ])
};

/**
 * Makes a places autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.sessiontoken Unique string identifying a single user's session. For convenience use require('@google/maps').util.placesAutoCompleteSessionToken()
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.types]
 * @param {Array<string>} [query.components]
 * @param {boolean} [query.strictbounds]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/autocomplete/json',
  validator: v.object({
    input: v.string,
    sessiontoken: v.optional(v.string),
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    types: v.optional(v.oneOf(['geocode', 'address', 'establishment', '(regions)', '(cities)'])),
    components: v.optional(utils.pipedKeyValues),
    strictbounds: v.optional(v.boolean),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a places query autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesQueryAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesQueryAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json',
  validator: v.object({
    input: v.string,
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};
<commit_msg>Fixes error in documentation for autocomplete<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */;

var utils = require('../internal/convert');
var v = require('../internal/validate');

/**
 * A Find Place request takes a text input, and returns a place.
 * The text input can be any kind of Places data, for example,
 * a name, address, or phone number.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.findPlace
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.inputtype
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.findPlace = {
  url: 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json',
  validator: v.compose([
    v.object({
      input: v.string,
      inputtype: v.oneOf(['textquery', 'phonenumber']),
      language: v.optional(v.string),
      fields: v.optional(utils.arrayOf(v.oneOf([
        'formatted_address', 'geometry', 'icon', 'id', 'name',
        'permanently_closed', 'photos', 'place_id', 'scope', 'types',
        'vicinity', 'opening_hours', 'price_level', 'rating'
      ]), ',')),
      locationbias: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    }),
    function(query) {
      if (!query.locationbias || query.locationbias == 'ipbias') {
        return query;
      }
      var isLatLng = function(latLng) {
        latLng = latLng.split(',');
        return latLng.length == 2 && !isNaN(latLng[0]) && !isNaN(latLng[1]);
      };
      var parts = query.locationbias.split(':');
      switch (parts[0]) {
        case 'point':
          if (isLatLng(parts[parts.length - 1])) {
            return query;
          }
          break;
        case 'circle':
          parts = parts[parts.length - 1].split('@');
          if (!isNaN(parts[0]) && isLatLng(parts[parts.length - 1])) {
            return query;
          }
          break;
        case 'rectangle':
          parts = parts[parts.length - 1].split('|');
          if (parts.length == 2 && isLatLng(parts[0]) && isLatLng(parts[1])) {
            return query;
          }
          break;
      }
      throw new v.InvalidValueError('invalid locationbias');
    }
  ])
};

/**
 * Makes a places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.places
 * @function
 * @param {Object} query
 * @param {string} query.query
 * @param {string} [query.language]
 * @param {LatLng} [query.location]
 * @param {number} [query.radius]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {string} [query.region]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.places = {
  url: 'https://maps.googleapis.com/maps/api/place/textsearch/json',
  validator: v.object({
    query: v.optional(v.string),
    language: v.optional(v.string),
    location: v.optional(utils.latLng),
    radius: v.optional(v.number),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    pagetoken: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number),
    region: v.optional(v.string)
  })
};

/**
 * Makes a nearby places request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesNearby
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.rankby] Either 'prominence' or 'distance'
 * @param {string} [query.type]
 * @param {string} [query.pagetoken]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesNearby = {
  url: 'https://maps.googleapis.com/maps/api/place/nearbysearch/json',
  validator: v.compose([
    v.mutuallyExclusivePropertiesRequired(['location', 'pagetoken']),
    v.object({
      location: v.optional(utils.latLng),
      language: v.optional(v.string),
      radius: v.optional(v.number),
      keyword: v.optional(v.string),
      minprice: v.optional(v.number),
      maxprice: v.optional(v.number),
      name: v.optional(v.string),
      opennow: v.optional(v.boolean),
      rankby: v.optional(v.oneOf(['prominence', 'distance'])),
      type: v.optional(v.string),
      pagetoken: v.optional(v.string),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    })
  ])
};

/**
 * Makes a places radar search request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesRadar
 * @function
 * @param {Object} query
 * @param {LatLng} query.location
 * @param {number} query.radius
 * @param {string} [query.language]
 * @param {string} [query.keyword]
 * @param {number} [query.minprice]
 * @param {number} [query.maxprice]
 * @param {string} [query.name]
 * @param {boolean} [query.opennow]
 * @param {string} [query.type]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesRadar = {
  url: 'https://maps.googleapis.com/maps/api/place/radarsearch/json',
  validator: v.object({
    location: utils.latLng,
    radius: v.number,
    language: v.optional(v.string),
    keyword: v.optional(v.string),
    minprice: v.optional(v.number),
    maxprice: v.optional(v.number),
    name: v.optional(v.string),
    opennow: v.optional(v.boolean),
    type: v.optional(v.string),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place detail request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.place
 * @function
 * @param {Object} query
 * @param {string} query.placeid
 * @param {string} query.sessiontoken Unique string identifying a single user's session. For convenience use require('@google/maps').util.placesAutoCompleteSessionToken()
 * @param {string} [query.language]
 * @param {Array<string>} [query.fields]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.place = {
  url: 'https://maps.googleapis.com/maps/api/place/details/json',
  validator: v.object({
    placeid: v.string,
    sessiontoken: v.optional(v.string),
    language: v.optional(v.string),
    fields: v.optional(utils.arrayOf(v.oneOf([
      'address_component', 'adr_address', 'alt_id', 'formatted_address',
      'geometry', 'icon', 'id', 'name', 'permanently_closed', 'photo',
      'place_id', 'scope', 'type', 'url', 'utc_offset', 'vicinity',
      'formatted_phone_number', 'international_phone_number', 'opening_hours',
      'website', 'price_level', 'rating', 'review', 'user_ratings_total', 'plus_code'
    ]), ',')),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a place photos request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesPhoto
 * @function
 * @param {Object} query
 * @param {string} query.photoreference
 * @param {number} [query.maxwidth]
 * @param {number} [query.maxheight]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesPhoto = {
  url: 'https://maps.googleapis.com/maps/api/place/photo',
  validator: v.compose([
    v.atLeastOneOfProperties(['maxwidth', 'maxheight']),
    v.object({
      photoreference: v.string,
      maxwidth: v.optional(v.number),
      maxheight: v.optional(v.number),
      retryOptions: v.optional(utils.retryOptions),
      timeout: v.optional(v.number)
    })
  ])
};

/**
 * Makes a places autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {string} query.sessiontoken Unique string identifying a single user's session. For convenience use require('@google/maps').util.placesAutoCompleteSessionToken()
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {string} [query.types]
 * @param {Object} components
 * @param {boolean} [query.strictbounds]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/autocomplete/json',
  validator: v.object({
    input: v.string,
    sessiontoken: v.optional(v.string),
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    types: v.optional(v.oneOf(['geocode', 'address', 'establishment', '(regions)', '(cities)'])),
    components: v.optional(utils.pipedKeyValues),
    strictbounds: v.optional(v.boolean),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};

/**
 * Makes a places query autocomplete request.
 *
 * @memberof! GoogleMapsClient
 * @name GoogleMapsClient.placesQueryAutoComplete
 * @function
 * @param {Object} query
 * @param {string} query.input
 * @param {number} [query.offset]
 * @param {LatLng} [query.location]
 * @param {string} [query.language]
 * @param {number} [query.radius]
 * @param {ResponseCallback} callback Callback function for handling the result
 * @return {RequestHandle}
 */
exports.placesQueryAutoComplete = {
  url: 'https://maps.googleapis.com/maps/api/place/queryautocomplete/json',
  validator: v.object({
    input: v.string,
    offset: v.optional(v.number),
    location: v.optional(utils.latLng),
    language: v.optional(v.string),
    radius: v.optional(v.number),
    retryOptions: v.optional(utils.retryOptions),
    timeout: v.optional(v.number)
  })
};
<|endoftext|>


<commit_before>var moment          = require('moment'),
    util            = require('util'),
    cfg             = require('nconf'),
    _s              = require('underscore.string'),
    hash            = cfg.get('hash'),
    labelUrlFormat  = cfg.get('app:labelUrl'),
    pgnUrl          = cfg.get('app:paginationUrl'),
    pgnRegexp       = new RegExp(pgnUrl.replace(':page', '\\d+'));

/*
 * Builds post or static page URL.
 * It is assumed that 'this' is the current resonse
 */
function postUrl (post, absolute) {
    if (arguments.length < 2 && 'boolean' === typeof post) {
        absolute = post;
        post = this.locals.post;
    }

    var output = post.page ? cfg.get('app:pageUrl') : cfg.get('app:postUrl'),
        tags = {
            ':year':   function () { return moment(post.publishedAt).format('YYYY'); },
            ':month':  function () { return moment(post.publishedAt).format('MM'); },
            ':day':    function () { return moment(post.publishedAt).format('DD'); },
            ':slug':   function () { return post.slug; },
            ':id':     function () { return post.id || post._id; }
        };

    // replace tags like :slug or :year with actual values
    output = output.replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) {
            return tags[match]();
        }
    });

    return absolute ? cfg.get('url') + output : output;
}

function labelUrl (label, absolute) {
    return (absolute ? cfg.get('url') : '') +
        labelUrlFormat.replace(':label', encodeURIComponent(label));
}

/*
 * Builds pagination URL
 * It assumes that 'this' is current response
 */
function pageUrl (newer) {
    var pagination = this.locals.pagination,
        page = newer ? pagination.newer : pagination.older,
        ctx = this.locals.context.replace(pgnRegexp, '');

    return 1 === page ? ('' === ctx ? '/' : ctx)
        : ('/' === ctx ? '' : ctx) + pgnUrl.replace(':page', page);
}

/*
 * Formats given date. If the given date is a post then 'publishedAt' date is taken for formatting.
 */
function dateFormat (post, format) {
    if (arguments.length < 2) {
        if('string' === typeof post) {
            format = post;
            post = this.locals.post;
        } else {
            format = "YYYY-MM-DD";
        }
    }

    var date = util.isDate(post) || moment.isMoment(post) ? post : post && post.publishedAt;

    if (!date) return '';

    var mdate = moment(date);

    switch (format) {
        case 'timeago': return mdate.fromNow();
        case 'utc': return mdate.utc();
        case 'iso': return mdate.toISOString();
        default: return mdate.format(format);
    }
}

function labelsFormat (post, join) {
    if(2 > arguments.length && 'string' === typeof post) {
        join = post;
        post = this.locals.post;
    }

    return post.labels.join(join || ', ');
}

function metaTitle () {
    var post = this.locals.post,
        tokens = {
            ':blogtitle': function () { return cfg.get('app:title'); },
            ':posttitle': function () { return post.title; },
            ':authorname': function () { return post.author ? post.author.name : ''; }
        };

    return (post) ?
        cfg.get('app:postMetaTitleFormat').replace(/:[a-z]+/ig, function (token) {
            if (token in tokens) return tokens[token]();
        })
        : cfg.get('app:metaTitle') || cfg.get('app:title');
}

function metaDescription () {
    return cfg.get('app:metaDescription') || cfg.get('app:description');
}

function encode (text) {
    return encodeURIComponent(text);
}

function labelToClass (labels) {
    return (labels || []).map(function (label) {
        return 'tag-' + _s.slugify(label);
    });
}

function buildBodyClass () {
    // Main page template
    if ('/' === this.locals.context) return 'home-template';

    // Any pagination template
    if (+this.req.params.page) return 'archive-template';

    // Post or static page template (when res.locals.post is present)
    var post = this.locals.post;
    if (post) {
        var bodyClass = labelToClass(post.labels);
        bodyClass.push('post-template');

        // If post is actually a static page
        if (post.page) bodyClass.push('page');

        return bodyClass;
    }
}

function buildPostClass (post) {
    post = post || this.locals.post;

    var postClass = ['post'];

    if(post) {
        Array.prototype.push.apply(postClass, labelToClass(post.labels));
    }

    return postClass;
}

function copyright () {
    var app = this,
        tags = {
            ':year' : function () { return moment().format('YYYY'); },
            ':url'  : function () { return app.locals.url; },
            ':title': function () { return app.locals.title; }
        };
    return cfg.get('app:copyright').replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) return tags[match]();
    });
}

function author () {
    return this.locals.post && this.locals.post.author;
}

function ifCheck (value, element, checkValue) {
    return element === checkValue ? value : undefined;
}

/*
 * Generates an asset URL.
 * It assumes that 'this' is current application
 */
function assets (asset) {
    return '/assets' + ('/' === asset[0] ? '' : '/') + asset + '?v=ad2e223fd';
}

function adminTheme (asset) {
    return adminAsset('/client/css/theme/' + cfg.get('admin:theme') + '/' + asset);
}

function adminAsset (asset) {
    if (!hash) return asset;

    var parts = asset.split('/');
    parts.splice(2, 0, hash);

    return parts.join('/');
}

function isntEmpty(obj, prop) {
    var value;

    if(arguments.length === 1) value = obj;
    else if (arguments.length === 2) value = obj[prop];

    if (!value) return false;
    if ('string' === typeof value || util.isArray(value)) return value.length > 0;

    return true;
}

function initRequest (req, res, next) {
    // Set default response local variables

    Object.defineProperties(res.locals, {
        context:    { enumerable: true, value: req.path },
        now:        { enumerable: true, value: moment() },
        $postClass: { enumerable: true, value: buildPostClass.bind(res) },
        $url:       { enumerable: true, value: postUrl.bind(res) },
        $pageUrl:   { enumerable: true, value: pageUrl.bind(res) },
        $labels:    { enumerable: true, value: labelsFormat.bind(res) },
        $date:      { enumerable: true, value: dateFormat.bind(res) },
        bodyClass:  { enumerable: true, get: buildBodyClass.bind(res) },
        postClass:  { enumerable: true, get: buildPostClass.bind(res) },
        metaTitle:  { enumerable: true, get: metaTitle.bind(res) },
        metaDescription: { enumerable: true, get: metaDescription.bind(res) },
        author:     { enumerable: true, get: author.bind(res) }
    });

    next();
}

function init (app) {
    // Set default application local variables as well as template helper functions
    Object.defineProperties(app.locals, {
        custom:      { enumerable: true, value: cfg.get('theme:custom') },
        title:       { enumerable: true, value: cfg.get('app:title') },
        description: { enumerable: true, value: cfg.get('app:description') },
        url:         { enumerable: true, value: cfg.get('url') },
        cover:       { enumerable: true, value: cfg.get('theme:cover') },
        logo:        { enumerable: true, value: cfg.get('theme:logo') },
        version:     { enumerable: true, value: module.exports.version },
        copyright:   { enumerable: true, get: copyright.bind(app) },
        $encode:     { enumerable: true, value: encode },
        $assets:     { enumerable: true, value: assets.bind(app) },
        $if:         { enumerable: true, value: ifCheck },
        $isntEmpty:  { enumerable: true, value: isntEmpty },
        $labelUrl:   { enumerable: true, value: labelUrl },
        $adminTheme: { enumerable: true, value: adminTheme },
        $adminAsset: { enumerable: true, value: adminAsset },

        // Update application locals with view settings like debug or pretty formatting
        pretty:      { enumerable: true, value: cfg.get('view:pretty') },
        debug:       { enumerable: true, value: cfg.get('view:debug') }
    });

    // Initialize reqest / response specific variables
    app.use(initRequest);
}

module.exports = init;

require('pkginfo')(module, 'version');<commit_msg>Logged in user added to the template variables<commit_after>var moment          = require('moment'),
    util            = require('util'),
    cfg             = require('nconf'),
    _s              = require('underscore.string'),
    hash            = cfg.get('hash'),
    labelUrlFormat  = cfg.get('app:labelUrl'),
    pgnUrl          = cfg.get('app:paginationUrl'),
    pgnRegexp       = new RegExp(pgnUrl.replace(':page', '\\d+'));

/*
 * Builds post or static page URL.
 * It is assumed that 'this' is the current resonse
 */
function postUrl (post, absolute) {
    if (arguments.length < 2 && 'boolean' === typeof post) {
        absolute = post;
        post = this.locals.post;
    }

    var output = post.page ? cfg.get('app:pageUrl') : cfg.get('app:postUrl'),
        tags = {
            ':year':   function () { return moment(post.publishedAt).format('YYYY'); },
            ':month':  function () { return moment(post.publishedAt).format('MM'); },
            ':day':    function () { return moment(post.publishedAt).format('DD'); },
            ':slug':   function () { return post.slug; },
            ':id':     function () { return post.id || post._id; }
        };

    // replace tags like :slug or :year with actual values
    output = output.replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) {
            return tags[match]();
        }
    });

    return absolute ? cfg.get('url') + output : output;
}

function labelUrl (label, absolute) {
    return (absolute ? cfg.get('url') : '') +
        labelUrlFormat.replace(':label', encodeURIComponent(label));
}

/*
 * Builds pagination URL
 * It assumes that 'this' is current response
 */
function pageUrl (newer) {
    var pagination = this.locals.pagination,
        page = newer ? pagination.newer : pagination.older,
        ctx = this.locals.context.replace(pgnRegexp, '');

    return 1 === page ? ('' === ctx ? '/' : ctx)
        : ('/' === ctx ? '' : ctx) + pgnUrl.replace(':page', page);
}

/*
 * Formats given date. If the given date is a post then 'publishedAt' date is taken for formatting.
 */
function dateFormat (post, format) {
    if (arguments.length < 2) {
        if('string' === typeof post) {
            format = post;
            post = this.locals.post;
        } else {
            format = "YYYY-MM-DD";
        }
    }

    var date = util.isDate(post) || moment.isMoment(post) ? post : post && post.publishedAt;

    if (!date) return '';

    var mdate = moment(date);

    switch (format) {
        case 'timeago': return mdate.fromNow();
        case 'utc': return mdate.utc();
        case 'iso': return mdate.toISOString();
        default: return mdate.format(format);
    }
}

function labelsFormat (post, join) {
    if(2 > arguments.length && 'string' === typeof post) {
        join = post;
        post = this.locals.post;
    }

    return post.labels.join(join || ', ');
}

function metaTitle () {
    var post = this.locals.post,
        tokens = {
            ':blogtitle': function () { return cfg.get('app:title'); },
            ':posttitle': function () { return post.title; },
            ':authorname': function () { return post.author ? post.author.name : ''; }
        };

    return (post) ?
        cfg.get('app:postMetaTitleFormat').replace(/:[a-z]+/ig, function (token) {
            if (token in tokens) return tokens[token]();
        })
        : cfg.get('app:metaTitle') || cfg.get('app:title');
}

function metaDescription () {
    return cfg.get('app:metaDescription') || cfg.get('app:description');
}

function encode (text) {
    return encodeURIComponent(text);
}

function labelToClass (labels) {
    return (labels || []).map(function (label) {
        return 'tag-' + _s.slugify(label);
    });
}

function buildBodyClass () {
    // Main page template
    if ('/' === this.locals.context) return 'home-template';

    // Any pagination template
    if (+this.req.params.page) return 'archive-template';

    // Post or static page template (when res.locals.post is present)
    var post = this.locals.post;
    if (post) {
        var bodyClass = labelToClass(post.labels);
        bodyClass.push('post-template');

        // If post is actually a static page
        if (post.page) bodyClass.push('page');

        return bodyClass;
    }
}

function buildPostClass (post) {
    post = post || this.locals.post;

    var postClass = ['post'];

    if(post) {
        Array.prototype.push.apply(postClass, labelToClass(post.labels));
    }

    return postClass;
}

function copyright () {
    var app = this,
        tags = {
            ':year' : function () { return moment().format('YYYY'); },
            ':url'  : function () { return app.locals.url; },
            ':title': function () { return app.locals.title; }
        };
    return cfg.get('app:copyright').replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) return tags[match]();
    });
}

function author () {
    return this.locals.post && this.locals.post.author;
}

function ifCheck (value, element, checkValue) {
    return element === checkValue ? value : undefined;
}

/*
 * Generates an asset URL.
 * It assumes that 'this' is current application
 */
function assets (asset) {
    return '/assets' + ('/' === asset[0] ? '' : '/') + asset + '?v=ad2e223fd';
}

function adminTheme (asset) {
    return adminAsset('/client/css/theme/' + cfg.get('admin:theme') + '/' + asset);
}

function adminAsset (asset) {
    if (!hash) return asset;

    var parts = asset.split('/');
    parts.splice(2, 0, hash);

    return parts.join('/');
}

function isntEmpty(obj, prop) {
    var value;

    if(arguments.length === 1) value = obj;
    else if (arguments.length === 2) value = obj[prop];

    if (!value) return false;
    if ('string' === typeof value || util.isArray(value)) return value.length > 0;

    return true;
}

function initRequest (req, res, next) {
    // Set default response local variables

    Object.defineProperties(res.locals, {
        context:    { enumerable: true, value: req.path },
        now:        { enumerable: true, value: moment() },
        $postClass: { enumerable: true, value: buildPostClass.bind(res) },
        $url:       { enumerable: true, value: postUrl.bind(res) },
        $pageUrl:   { enumerable: true, value: pageUrl.bind(res) },
        $labels:    { enumerable: true, value: labelsFormat.bind(res) },
        $date:      { enumerable: true, value: dateFormat.bind(res) },
        bodyClass:  { enumerable: true, get: buildBodyClass.bind(res) },
        postClass:  { enumerable: true, get: buildPostClass.bind(res) },
        metaTitle:  { enumerable: true, get: metaTitle.bind(res) },
        metaDescription: { enumerable: true, get: metaDescription.bind(res) },
        author:     { enumerable: true, get: author.bind(res) },
        user:       { enumerable: true, value: req.user }
    });

    next();
}

function init (app) {
    // Set default application local variables as well as template helper functions
    Object.defineProperties(app.locals, {
        custom:      { enumerable: true, value: cfg.get('theme:custom') },
        title:       { enumerable: true, value: cfg.get('app:title') },
        description: { enumerable: true, value: cfg.get('app:description') },
        url:         { enumerable: true, value: cfg.get('url') },
        cover:       { enumerable: true, value: cfg.get('theme:cover') },
        logo:        { enumerable: true, value: cfg.get('theme:logo') },
        version:     { enumerable: true, value: module.exports.version },
        copyright:   { enumerable: true, get: copyright.bind(app) },
        $encode:     { enumerable: true, value: encode },
        $assets:     { enumerable: true, value: assets.bind(app) },
        $if:         { enumerable: true, value: ifCheck },
        $isntEmpty:  { enumerable: true, value: isntEmpty },
        $labelUrl:   { enumerable: true, value: labelUrl },
        $adminTheme: { enumerable: true, value: adminTheme },
        $adminAsset: { enumerable: true, value: adminAsset },

        // Update application locals with view settings like debug or pretty formatting
        pretty:      { enumerable: true, value: cfg.get('view:pretty') },
        debug:       { enumerable: true, value: cfg.get('view:debug') }
    });

    // Initialize reqest / response specific variables
    app.use(initRequest);
}

module.exports = init;

require('pkginfo')(module, 'version');<|endoftext|>


<commit_before>var moment        = require('moment'),
    cfg           = require('nconf'),
    _s            = require('underscore.string'),
    pgnUrl        = cfg.get('app:pageUrlFormat'),
    pgnRegexp     = new RegExp(pgnUrl.replace(/\//g, '\\/').replace(':page', '\\d+'));

/*
 * Builds post or static page URL
 */
function postUrl (post, absolute) {
    if (arguments.length < 2 && 'boolean' === typeof post) {
        absolute = post;
        post = this.locals.post;
    }

    var output = post.page ? '/:slug' : cfg.get('app:urlFormat'),
        tags = {
            ':year':   function () { return moment(post.published_at).format('YYYY'); },
            ':month':  function () { return moment(post.published_at).format('MM'); },
            ':day':    function () { return moment(post.published_at).format('DD'); },
            ':slug':   function () { return post.slug; },
            ':id':     function () { return post.id; }
        };

    // replace tags like :slug or :year with actual values
    output = output.replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) {
            return tags[match]();
        }
    });

    return absolute ? cfg.get('url') + output : output;
};

/*
 * Builds pagination URL
 * It assumes that 'this' is current response
 */
function pageUrl (newer) {
    var pagination = this.locals.pagination,
        page = newer ? pagination.newer : pagination.older,
        ctx = this.locals.context.replace(pgnRegexp, '');

    if ('/' === ctx) ctx = '';

    return 1 === page ? '/' : ctx + pgnUrl.replace(':page', page);
}

/*
 * Formats scheduled date for the given post and date format
 */
function dateFormat (post, format) {
    return post.scheduled
        ? moment(post.scheduled).format(format || "YYYY-MM-DD")
        : '';
};

function labelsFormat (post, join) {
    if(2 > arguments.length && 'string' === typeof post) {
        join = post;
        post = this.locals.post;
    }

    return post.labels.join(join || ', ');
};

function metaTitle () {
    return cfg.get('app:defaultMetaTitle') || cfg.get('app:title');
}

function metaDescription () {
    return cfg.get('app:defaultMetaDesc') || cfg.get('app:description');
}

function metaKeywords () {
    return cfg.get('app:defaultKeywords');
}

function encode (text) {
    return encodeURIComponent(text);
}

function bodyClass () {
    if ('/' === this.locals.context) return 'home-template';
    if (+this.req.params.page) return 'archive-template';

    var post = this.locals.post;
    if (post) {
        var bodyClass = ['post-template'];

        if (post.page) bodyClass.push('page');

        (post.labels || []).forEach(function (label) {
            bodyClass.push('tag-' + _s.slugify(label));
        })

        return bodyClass;
    }
}

function postClass (post) {
    return "";
}

/*
 * Generates an asset URL.
 * It assumes that 'this' is current application
 */
function assets (asset) {
    return '/assets' + ('/' === asset[0] ? '' : '/') + asset + '?v=ad2e223fd'
}

function initRequest (req, res, next) {
    // Set default response local variables
    res.locals({
        context:    req.path,
        metaTitle:  metaTitle,
        $url:       postUrl.bind(res),
        $pageUrl:   pageUrl.bind(res),
        $labels:    labelsFormat.bind(res)
    })

    Object.defineProperties(res.locals, {
        "bodyClass": { enumerable: true, get: bodyClass.bind(res) },
        "metaTitle": { enumerable: true, get: metaTitle.bind(res) },
        "metaDescription": { enumerable: true, get: metaDescription.bind(res) },
        "metaKeywords":  { enumerable: true, get: metaKeywords.bind(res) }
    })

    next();
}

function init (app) {

    // Set default application local variables as well as template helper functions
    app.locals({
        title:          cfg.get('app:title'),
        description:    cfg.get('app:description'),
        url:            cfg.get('url'),
        copyright:      cfg.get('app:copyright'),
        cover:          '/assets/img/header.jpg',
        $date:          dateFormat,
        $encode:        encode,
        $postClass:     postClass,
        $assets:        assets.bind(app)
    })

    // Update application locals with view settings like debug or pretty formatting
    app.locals(cfg.get('view'));

    // Initialize reqest / response specific variables
    app.use(initRequest);
};

module.exports = init;<commit_msg>PostClass template helper method added; tags from labels generation improvements<commit_after>var moment        = require('moment'),
    cfg           = require('nconf'),
    _s            = require('underscore.string'),
    pgnUrl        = cfg.get('app:pageUrlFormat'),
    pgnRegexp     = new RegExp(pgnUrl.replace(/\//g, '\\/').replace(':page', '\\d+'));

/*
 * Builds post or static page URL
 */
function postUrl (post, absolute) {
    if (arguments.length < 2 && 'boolean' === typeof post) {
        absolute = post;
        post = this.locals.post;
    }

    var output = post.page ? '/:slug' : cfg.get('app:urlFormat'),
        tags = {
            ':year':   function () { return moment(post.published_at).format('YYYY'); },
            ':month':  function () { return moment(post.published_at).format('MM'); },
            ':day':    function () { return moment(post.published_at).format('DD'); },
            ':slug':   function () { return post.slug; },
            ':id':     function () { return post.id; }
        };

    // replace tags like :slug or :year with actual values
    output = output.replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) {
            return tags[match]();
        }
    });

    return absolute ? cfg.get('url') + output : output;
};

/*
 * Builds pagination URL
 * It assumes that 'this' is current response
 */
function pageUrl (newer) {
    var pagination = this.locals.pagination,
        page = newer ? pagination.newer : pagination.older,
        ctx = this.locals.context.replace(pgnRegexp, '');

    if ('/' === ctx) ctx = '';

    return 1 === page ? '/' : ctx + pgnUrl.replace(':page', page);
}

/*
 * Formats scheduled date for the given post and date format
 */
function dateFormat (post, format) {
    return post.scheduled
        ? moment(post.scheduled).format(format || "YYYY-MM-DD")
        : '';
};

function labelsFormat (post, join) {
    if(2 > arguments.length && 'string' === typeof post) {
        join = post;
        post = this.locals.post;
    }

    return post.labels.join(join || ', ');
};

function metaTitle () {
    return cfg.get('app:defaultMetaTitle') || cfg.get('app:title');
}

function metaDescription () {
    return cfg.get('app:defaultMetaDesc') || cfg.get('app:description');
}

function metaKeywords () {
    return cfg.get('app:defaultKeywords');
}

function encode (text) {
    return encodeURIComponent(text);
}

function labelToClass (post) {
    return (post.labels || []).map(function (label) {
        return 'tag-' + _s.slugify(label)
    })
}

function bodyClass () {
    if ('/' === this.locals.context) return 'home-template';
    if (+this.req.params.page) return 'archive-template';

    var post = this.locals.post;
    if (post) {
        var bodyClass = labelToClass(post);
        bodyClass.push('post-template');

        if (post.page) bodyClass.push('page');

        return bodyClass;
    }
}

function postClass (post) {
    post = post || this.locals.post;

    var postClass = labelToClass(post);
    postClass.push('post');

    return postClass;
}

/*
 * Generates an asset URL.
 * It assumes that 'this' is current application
 */
function assets (asset) {
    return '/assets' + ('/' === asset[0] ? '' : '/') + asset + '?v=ad2e223fd'
}

function initRequest (req, res, next) {
    // Set default response local variables
    res.locals({
        context:    req.path,
        metaTitle:  metaTitle,
        $postClass: postClass.bind(res),
        $url:       postUrl.bind(res),
        $pageUrl:   pageUrl.bind(res),
        $labels:    labelsFormat.bind(res)
    })

    Object.defineProperties(res.locals, {
        "bodyClass": { enumerable: true, get: bodyClass.bind(res) },
        "metaTitle": { enumerable: true, get: metaTitle.bind(res) },
        "metaDescription": { enumerable: true, get: metaDescription.bind(res) },
        "metaKeywords":  { enumerable: true, get: metaKeywords.bind(res) }
    })

    next();
}

function init (app) {

    // Set default application local variables as well as template helper functions
    app.locals({
        title:          cfg.get('app:title'),
        description:    cfg.get('app:description'),
        url:            cfg.get('url'),
        copyright:      cfg.get('app:copyright'),
        cover:          '/assets/img/header.jpg',
        $date:          dateFormat,
        $encode:        encode,
        $postClass:     postClass,
        $assets:        assets.bind(app)
    })

    // Update application locals with view settings like debug or pretty formatting
    app.locals(cfg.get('view'));

    // Initialize reqest / response specific variables
    app.use(initRequest);
};

module.exports = init;<|endoftext|>