<commit_before>// ==UserScript==
// @name        GitHub Download ZIP
// @version     0.2.1
// @description A userscript adds download links so that downloaded filenames include the SHA
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @run-at      document-idle
// @grant       GM_addStyle
// @grant       GM.addStyle
// @grant       GM_xmlhttpRequest
// @grant       GM.xmlHttpRequest
// @connect     api.github.com
// @connect     assets-cdn.github.com
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon        https://github.githubassets.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// ==/UserScript==
(() => {
	"use strict";

	GM.addStyle(`
		.ghdz-releases { width:100% !important; padding:10px; }
		.ghdz-releases summary { text-align:left; padding-left:16px; }
		.ghdz-files.select-menu-modal { width:100%; border:0; box-shadow:none !important; margin-bottom:0; }
		.ghdz-file { text-align:left; padding-left:16px; }
	`);

	const zipIcon = `
		<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" class="octicon" style="vertical-align:text-top;margin-left:4px">
			<path d="M28.7 7.2a28.4 28.4 0 0 0-5.9-5.9C21.2.1 20.4 0 20 0H4.5A2.5 2.5 0 0 0 2 2.5v27C2 30.9 3.1 32 4.5 32h23c1.4 0 2.5-1.1 2.5-2.5V10c0-.4-.1-1.2-1.3-2.8zm-4.2-1.7c1 1 1.8 1.8 2.3 2.5H22V3.2c.7.5 1.6 1.3 2.5 2.3zm3.5 24c0 .3-.2.5-.5.5h-23a.5.5 0 0 1-.5-.5v-27c0-.3.2-.5.5-.5H20v7c0 .6.4 1 1 1h7v19.5z"/>
			<path d="M8 2h4v2H8V2zM12 4h4v2h-4V4zM8 6h4v2H8V6zM12 8h4v2h-4V8zM8 10h4v2H8v-2zM12 12h4v2h-4v-2zM8 14h4v2H8v-2zM12 16h4v2h-4v-2zM8 26.5c0 .8.7 1.5 1.5 1.5h5c.8 0 1.5-.7 1.5-1.5v-5c0-.8-.7-1.5-1.5-1.5H12v-2H8v8.5zm6-2.5v2h-4v-2h4z"/>
		</svg>`;
	const span = document.createElement("span");
	span.innerHTML = zipIcon;

	const link = document.createElement("a");
	link.className = "btn btn-outline BtnGroup-item tooltipped tooltipped-s ghdz-btn";
	link.setAttribute("aria-label", "Download ZIP");
	link.innerHTML = zipIcon;

	const div = document.createElement("details");
	div.className = "select-menu get-repo-btn ghdz-releases";
	div.innerHTML = `

			Latest Release Files

		`;

	function buildURL(part) {
		const [, user, repo] = window.location.pathname.split("/");
		return `https://api.github.com/repos/${user}/${repo}/zipball/${part}`;
	}

	function buildReleases(result, container) {
		let html = "";
		if (Array.isArray(result)) {
			const assets = result[0] && Object.keys(result[0]).length && result[0].assets;
		  if (assets) {
				assets.forEach(release => {
					const url = release.browser_download_url;
					html += `

							${url.split("/").slice(-1)}
						`;
				});
		  } else {
				html = "
No releases found
";
		  }
		} else {
			// Error message
			html = result;
		}
		$(".ghdz-files", container).innerHTML = html;
	}

	function getReleases(container) {
		if ($(".ghdz-file", container)) {
			// Already loaded
			return;
		}
		const [, user, repo] = window.location.pathname.split("/");
		GM.xmlHttpRequest({
			method : "GET",
			url : `https://api.github.com/repos/${user}/${repo}/releases`,
			onload : response => {
				if (response.status !== 200) {
					buildReleases(response.message, container);
					return console.error(response);
				}
				let json = false;
				try {
					json = JSON.parse(response.responseText);
				} catch (err) {
					return console.error(response);
				}
				if (json) {
					buildReleases(json, container);
				}
			}
		});
	}

	function addBindings() {
		document.addEventListener("click", function(event) {
			const target = event.target.closest("details");
			if (target.classList.contains("ghdz-releases")) {
				getReleases(target);
			}
		});
	}

	function updateLinks() {
		// Branch dropdown on main repo page
		const branch = $("summary[data-hotkey='w']");
		// Download link in "Clone or Download" dropdown
		const downloadLink = $("a[data-ga-click*='download zip']");
		// Repo commits page
		const commits = $(".commits-listing");

		if (downloadLink && branch && !$(".ghdz-releases", downloadLink.parentNode)) {
			downloadLink.href = buildURL(branch.title.trim());
			downloadLink.appendChild(span.cloneNode(true));
			downloadLink.after(div.cloneNode(true));
		}
		// Branch doesn't matter when you're using the SHA (first 7 values)
		if (commits) {
			[...document.querySelectorAll(".commit-group .commit .commit-links-group")].forEach(group => {
				if (!$(".ghdz-btn", group)) {
					const sha = $(".sha", group).textContent.trim();
					const a = link.cloneNode(true);
					a.href = buildURL(sha);
					group.appendChild(a);
				}
			});
		}
	}

	function $(selector, el) {
		return (el || document).querySelector(selector);
	}

	// DOM targets - to detect GitHub dynamic ajax page loading
	document.addEventListener("pjax:end", updateLinks);
	updateLinks();
	addBindings();

})();
<commit_msg>Download-zip: Prevent truncated branch name usage. See #81<commit_after>// ==UserScript==
// @name        GitHub Download ZIP
// @version     0.2.2
// @description A userscript adds download links so that downloaded filenames include the SHA
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @run-at      document-idle
// @grant       GM_addStyle
// @grant       GM.addStyle
// @grant       GM_xmlhttpRequest
// @grant       GM.xmlHttpRequest
// @connect     api.github.com
// @connect     assets-cdn.github.com
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon        https://github.githubassets.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// ==/UserScript==
(() => {
	"use strict";

	GM.addStyle(`
		.ghdz-releases { width:100% !important; padding:10px; }
		.ghdz-releases summary { text-align:left; padding-left:16px; }
		.ghdz-files.select-menu-modal { width:100%; border:0; box-shadow:none !important; margin-bottom:0; }
		.ghdz-file { text-align:left; padding-left:16px; }
	`);

	const zipIcon = `
		<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" class="octicon" style="vertical-align:text-top;margin-left:4px">
			<path d="M28.7 7.2a28.4 28.4 0 0 0-5.9-5.9C21.2.1 20.4 0 20 0H4.5A2.5 2.5 0 0 0 2 2.5v27C2 30.9 3.1 32 4.5 32h23c1.4 0 2.5-1.1 2.5-2.5V10c0-.4-.1-1.2-1.3-2.8zm-4.2-1.7c1 1 1.8 1.8 2.3 2.5H22V3.2c.7.5 1.6 1.3 2.5 2.3zm3.5 24c0 .3-.2.5-.5.5h-23a.5.5 0 0 1-.5-.5v-27c0-.3.2-.5.5-.5H20v7c0 .6.4 1 1 1h7v19.5z"/>
			<path d="M8 2h4v2H8V2zM12 4h4v2h-4V4zM8 6h4v2H8V6zM12 8h4v2h-4V8zM8 10h4v2H8v-2zM12 12h4v2h-4v-2zM8 14h4v2H8v-2zM12 16h4v2h-4v-2zM8 26.5c0 .8.7 1.5 1.5 1.5h5c.8 0 1.5-.7 1.5-1.5v-5c0-.8-.7-1.5-1.5-1.5H12v-2H8v8.5zm6-2.5v2h-4v-2h4z"/>
		</svg>`;
	const span = document.createElement("span");
	span.innerHTML = zipIcon;

	const link = document.createElement("a");
	link.className = "btn btn-outline BtnGroup-item tooltipped tooltipped-s ghdz-btn";
	link.setAttribute("aria-label", "Download ZIP");
	link.innerHTML = zipIcon;

	const div = document.createElement("details");
	div.className = "select-menu get-repo-btn ghdz-releases";
	div.innerHTML = `

			Latest Release Files

		`;

	function buildURL(part) {
		const [, user, repo] = window.location.pathname.split("/");
		return `https://api.github.com/repos/${user}/${repo}/zipball/${part}`;
	}

	function buildReleases(result, container) {
		let html = "";
		if (Array.isArray(result)) {
			const assets = result[0] && Object.keys(result[0]).length && result[0].assets;
		  if (assets) {
				assets.forEach(release => {
					const url = release.browser_download_url;
					html += `

							${url.split("/").slice(-1)}
						`;
				});
		  } else {
				html = "
No release files found
";
		  }
		} else {
			// Error message
			html = result;
		}
		$(".ghdz-files", container).innerHTML = html;
	}

	function getReleases(container) {
		if ($(".ghdz-file", container)) {
			// Already loaded
			return;
		}
		const [, user, repo] = window.location.pathname.split("/");
		GM.xmlHttpRequest({
			method : "GET",
			url : `https://api.github.com/repos/${user}/${repo}/releases`,
			onload : response => {
				if (response.status !== 200) {
					buildReleases(response.message, container);
					return console.error(response);
				}
				let json = false;
				try {
					json = JSON.parse(response.responseText);
				} catch (err) {
					return console.error(response);
				}
				if (json) {
					buildReleases(json, container);
				}
			}
		});
	}

	function addBindings() {
		document.addEventListener("click", function(event) {
			const target = event.target.closest("details");
			if (target.classList.contains("ghdz-releases")) {
				getReleases(target);
			}
		});
	}

	function updateLinks() {
		// Branch dropdown on main repo page
		const branch = $("summary[data-hotkey='w'] span");
		// Download link in "Clone or Download" dropdown
		const downloadLink = $("a[data-ga-click*='download zip']");
		// Repo commits page
		const commits = $(".commits-listing");

		if (downloadLink && branch && !$(".ghdz-releases", downloadLink.parentNode)) {
			const branchName = branch.textContent.indexOf('…') > -1
				// Branch selector is showing trucated text; title has full text
				? branch.parentNode.title
				: branch.textContent;
			downloadLink.href = buildURL(branchName.trim());
			downloadLink.appendChild(span.cloneNode(true));
			downloadLink.after(div.cloneNode(true));
		}
		// Branch doesn't matter when you're using the SHA (first 7 values)
		if (commits) {
			[...document.querySelectorAll(".commit-group .commit .commit-links-group")].forEach(group => {
				if (!$(".ghdz-btn", group)) {
					const sha = $(".sha", group).textContent.trim();
					const a = link.cloneNode(true);
					a.href = buildURL(sha);
					group.appendChild(a);
				}
			});
		}
	}

	function $(selector, el) {
		return (el || document).querySelector(selector);
	}

	// DOM targets - to detect GitHub dynamic ajax page loading
	document.addEventListener("pjax:end", updateLinks);
	updateLinks();
	addBindings();

})();
<|endoftext|>


<commit_before>// Load in our dependencies
var assert = require('assert');
var app = require('electron').app;
var BrowserWindow = require('electron').BrowserWindow;

// Find the argv matching our URL pattern
// DEV: If this becomes complex, use `commander` with `allowUnknownOption`
var urlArgs = process.argv.filter(function matchUrlPattern (arg) {
  return /^--url=/.test(arg);
});
assert(urlArgs.length, 1, 'Expected only 1 `--url=` parameter but received ' + urlArgs.length);
var url = urlArgs[0].replace('--url=', '');

// When all windows are closed, exit out
app.on('window-all-closed', function handleWindowsClosed () {
  app.quit();
});

// When Electron is done loading, launch our applicaiton
app.on('ready', function handleReady () {
  // Create our browser window
  var browserWindow = new BrowserWindow({show: false});
  browserWindow.loadURL(url);
});
<commit_msg>Added TODO<commit_after>// Load in our dependencies
var assert = require('assert');
var app = require('electron').app;
var BrowserWindow = require('electron').BrowserWindow;

// Find the argv matching our URL pattern
// DEV: If this becomes complex, use `commander` with `allowUnknownOption`
var urlArgs = process.argv.filter(function matchUrlPattern (arg) {
  return /^--url=/.test(arg);
});
assert(urlArgs.length, 1, 'Expected only 1 `--url=` parameter but received ' + urlArgs.length);
var url = urlArgs[0].replace('--url=', '');

// When all windows are closed, exit out
app.on('window-all-closed', function handleWindowsClosed () {
  app.quit();
});

// When Electron is done loading, launch our applicaiton
app.on('ready', function handleReady () {
  // Create our browser window
  // TODO: Where does "Chrome" version come from
  var browserWindow = new BrowserWindow({show: false});
  browserWindow.loadURL(url);
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Externs for Blockly generators.
 * @externs
 */

goog.provide('Blockly');
goog.provide('Blockly.Generator');
goog.provide('Blockly.utils.global');
goog.provide('Blockly.utils.string');

var Blockly;
<commit_msg>Add extern for inputTypes<commit_after>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Externs for Blockly generators.
 * @externs
 */

goog.provide('Blockly');
goog.provide('Blockly.Generator');
goog.provide('Blockly.inputTypes');
goog.provide('Blockly.utils.global');
goog.provide('Blockly.utils.string');

var Blockly;
<|endoftext|>


<commit_before>// ==UserScript==
// @name        GitHub Files Filter
// @version     1.1.4
// @description A userscript that adds filters that toggle the view of repo files by extension
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @run-at      document-idle
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addStyle
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=634242
// @icon        https://assets-cdn.github.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-files-filter.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-files-filter.user.js
// ==/UserScript==
(() => {
	"use strict";

	// Emphasize selected buttons, disable hover when all selected and remove
	// animation delay; See #46
	GM_addStyle(`
		.gff-filter .btn.selected { font-variant: small-caps; }
		.gff-filter .gff-all:not(.selected):focus,
		.gff-filter .gff-all:not(.selected) ~ .btn:focus,
		.gff-filter .gff-all:not(.selected) ~ .btn.selected:focus,
		.gff-filter .gff-all:not(.selected):hover,
		.gff-filter .gff-all:not(.selected) ~ .btn:hover,
		.gff-filter .gff-all:not(.selected) ~ .btn.selected:hover {
			border-color: #777 !important;
		}
		.gff-filter .btn:before, .gff-filter .btn:after {
			animation-delay: unset !important;
			filter: invert(10%);
		}
	`);

	let settings,
		list = {};
	const types = {
			// including ":" in key since it isn't allowed in a file name
			":all": {
				// return false to prevent adding files under this type
				is: () => false,
				text: "\u00ABall\u00BB"
			},
			":noExt": {
				is: name => !/\./.test(name),
				text: "\u00ABno-ext\u00BB"
			},
			":dot": {
				// this will include ".travis.yml"... should we add to "yml" instead?
				is: name => /^\./.test(name),
				text: "\u00ABdot-files\u00BB"
			},
			":min": {
				is: name => /\.min\./.test(name),
				text: "\u00ABmin\u00BB"
			}
		},
		// TODO: add toggle for submodule and dot-folders
		folderIconClasses = [
			".octicon-file-directory",
			".octicon-file-symlink-directory",
			".octicon-file-submodule"
		].join(",");

	// default to all file types visible; remember settings between sessions
	list[":all"] = true; // list gets cleared in buildList function
	settings = GM_getValue("gff-filter-settings", list);

	function updateFilter(event) {
		event.preventDefault();
		event.stopPropagation();
		const el = event.target;
		toggleBlocks(
			el.getAttribute("data-ext"),
			el.classList.contains("selected") ? "hide" : "show"
		);
	}

	function updateSettings(name, mode) {
		settings[name] = mode === "show";
		GM_setValue("gff-filter-settings", settings);
	}

	function updateAllButton() {
		if ($(".gff-filter")) {
			const buttons = $(".file-wrap .gff-filter"),
				filters = $$(".btn:not(.gff-all)", buttons),
				selected = $$(".btn:not(.gff-all).selected", buttons);
			// set "all" button
			$(".gff-all", buttons).classList.toggle(
				"selected",
				filters.length === selected.length
			);
		}
	}

	function toggleImagePreview(ext, mode) {
		if ($(".ghip-image-previews")) {
			let selector = "a",
				hasType = types[ext];
			if (!hasType) {
				selector += `[href$="${ext}"]`;
			}
			$$(`.ghip-image-previews ${selector}`).forEach(el => {
				if (!$(".ghip-folder, .ghip-up-tree", el)) {
					if (hasType && ext !== ":all") {
						// image preview includes the filename
						let elm = $(".ghip-file-name", el);
						if (elm && !hasType.is(elm.textContent)) {
							return;
						}
					}
					el.style.display = mode === "show" ? "" : "none";
				}
			});
		}
	}

	function toggleRow(el, mode) {
		const row = el.closest("tr.js-navigation-item");
		// don't toggle folders
		if (row && !$(folderIconClasses, row)) {
			row.style.display = mode === "show" ? "" : "none";
		}
	}

	function toggleAll(mode) {
		const files = $(".file-wrap");
		// Toggle "all" blocks
		$$("td.content .js-navigation-open", files).forEach(el => {
			toggleRow(el, mode);
		});
		// update filter buttons
		$$(".gff-filter .btn", files).forEach(el => {
			el.classList.toggle("selected", mode === "show");
		});
		updateSettings(":all", mode);
	}

	function toggleFilter(filter, mode) {
		const files = $(".file-wrap"),
			elm = $(`.gff-filter .btn[data-ext="${filter}"]`, files);
		/* list[filter] contains an array of file names */
		list[filter].forEach(name => {
			const el = $(`a[title="${name}"]`, files);
			if (el) {
				toggleRow(el, mode);
			}
		});
		if (elm) {
			elm.classList.toggle("selected", mode === "show");
		}
		updateSettings(filter, mode);
	}

	function toggleBlocks(filter, mode) {
		if (filter === ":all") {
			toggleAll(mode);
		} else if (list[filter]) {
			toggleFilter(filter, mode);
		}
		// update view for github-image-preview.user.js
		toggleImagePreview(filter, mode);
		updateAllButton();
	}

	function addExt(ext, txt) {
		if (ext) {
			if (!list[ext]) {
				list[ext] = [];
			}
			list[ext].push(txt);
		}
	}

	function buildList() {
		list = {};
		Object.keys(types).forEach(item => {
			if (item !== ":all") {
				list[item] = [];
			}
		});
		// get all files
		$$("table.files tr.js-navigation-item").forEach(file => {
			if ($("td.icon .octicon-file", file)) {
				let ext, parts, sub,
					link = $("td.content .js-navigation-open", file),
					txt = (link.title || link.textContent || "").trim().toLowerCase(),
					name = txt.split("/").slice(-1)[0];
				// test extension types; fallback to regex extraction
				ext = Object.keys(types).find(item => {
					return types[item].is(name);
				}) || /[^./\\]*$/.exec(name)[0];
				parts = name.split(".");
				if (!ext.startsWith(":") && parts.length > 2 && parts[0] !== "") {
					sub = parts.slice(0, -1).join(".");
					// Prevent version numbers & "vs. " from adding a filter button
					// See https://github.com/tpn/pdfs
					if (!/[()]/.test(sub) && !/[\b\w]\.[\b\d]/.test(sub)) {
						addExt(ext, txt);
						ext = parts.slice(-2).join(".");
					}
				}
				addExt(ext, txt);
			}
		});
	}

	function sortList() {
		return Object.keys(list).sort((a, b) => {
			// move ":" filters to the beginning, then sort the rest of the
			// extensions; test on https://github.com/rbsec/sslscan, where
			// the ".1" extension *was* appearing between ":" filters
			if (a[0] === ":") {
				return -1;
			}
			if (b[0] === ":") {
				return 1;
			}
			return a > b;
		});
	}

	function makeFilter() {
		let filters = 0;
		// get length, but don't count empty arrays
		Object.keys(list).forEach(ext => {
			filters += list[ext].length > 0 ? 1 : 0;
		});
		// Don't bother if only one extension is found
		const files = $(".file-wrap");
		if (files && filters > 1) {
			filters = $(".gff-filter-wrapper");
			if (!filters) {
				filters = document.createElement("div");
				// "commitinfo" allows GitHub-Dark styling
				filters.className = "gff-filter-wrapper commitinfo";
				filters.style = "padding:3px 5px 2px;border-bottom:1px solid #eaecef";
				files.insertBefore(filters, files.firstChild);
				filters.addEventListener("click", updateFilter);
			}
			fixWidth();
			buildHTML();
			applyInitSettings();
		}
	}

	function buildButton(name, label, ext, text) {
		return `<button type="button" ` +
			`class="btn btn-sm selected BtnGroup-item tooltipped tooltipped-n` +
			(name ? name : "") + `" ` +
			`data-ext="${ext}" aria-label="${label}">${text}</button>`;
	}

	function buildHTML() {
		let len,
			html = `<div class="BtnGroup gff-filter">` +
				// add a filter "all" button to the beginning
				buildButton(" gff-all", "Toggle all files", ":all", types[":all"].text);
		sortList().forEach(ext => {
			len = list[ext].length;
			if (len) {
				html += buildButton("", len, ext, types[ext] && types[ext].text || ext);
			}
		});
		// prepend filter buttons
		$(".gff-filter-wrapper").innerHTML = html + "
";
	}

	function getWidth(el) {
		return parseFloat(window.getComputedStyle(el).width);
	}

	// lock-in the table cell widths, or the navigation up link jumps when you
	// hide all files... using percentages in case someone is using GitHub wide
	function fixWidth() {
		let group, width,
			html = "",
			table = $("table.files"),
			tableWidth = getWidth(table),
			cells = $$("tbody:last-child tr:last-child td", table);
		if (table && cells.length > 1 && !$("colgroup", table)) {
			group = document.createElement("colgroup");
			table.insertBefore(group, table.childNodes[0]);
			cells.forEach(el => {
				// keep two decimal point accuracy
				width = parseInt(getWidth(el) / tableWidth * 1e4, 10) / 100;
				html += `<col style="width:${width}%">`;
			});
			group.innerHTML = html;
		}
	}

	function applyInitSettings() {
		// list doesn't include type.all entry
		if (settings[":all"] === false) {
			toggleBlocks(":all", "hide");
		} else {
			Object.keys(list).forEach(name => {
				if (settings[name] === false) {
					toggleBlocks(name, "hide");
				}
			});
		}
	}

	function init() {
		if ($("table.files")) {
			buildList();
			makeFilter();
		}
	}

	function $(str, el) {
		return (el || document).querySelector(str);
	}

	function $$(str, el) {
		return Array.from((el || document).querySelectorAll(str));
	}

	document.addEventListener("ghmo:container", () => {
		// init after a short delay to allow rendering of file list
		setTimeout(() => {
			init();
		}, 200);
	});
	init();

})();
<commit_msg>Update github-files-filter.user.js (#68)<commit_after>// ==UserScript==
// @name        GitHub Files Filter
// @version     1.1.5
// @description A userscript that adds filters that toggle the view of repo files by extension
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @run-at      document-idle
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addStyle
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=634242
// @icon        https://assets-cdn.github.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-files-filter.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-files-filter.user.js
// ==/UserScript==
(() => {
	"use strict";

	// Emphasize selected buttons, disable hover when all selected and remove
	// animation delay; See #46
	GM_addStyle(`
		.gff-filter .btn.selected { font-variant: small-caps; }
		.gff-filter .btn:not(.selected):not(:first-child) {
      			text-decoration: line-through;
		}
		.gff-filter .gff-all:not(.selected):focus,
		.gff-filter .gff-all:not(.selected) ~ .btn:focus,
		.gff-filter .gff-all:not(.selected) ~ .btn.selected:focus,
		.gff-filter .gff-all:not(.selected):hover,
		.gff-filter .gff-all:not(.selected) ~ .btn:hover,
		.gff-filter .gff-all:not(.selected) ~ .btn.selected:hover {
			border-color: #777 !important;
		}
		.gff-filter .btn:before, .gff-filter .btn:after {
			animation-delay: unset !important;
			filter: invert(10%);
		}
	`);

	let settings,
		list = {};
	const types = {
			// including ":" in key since it isn't allowed in a file name
			":all": {
				// return false to prevent adding files under this type
				is: () => false,
				text: "\u00ABall\u00BB"
			},
			":noExt": {
				is: name => !/\./.test(name),
				text: "\u00ABno-ext\u00BB"
			},
			":dot": {
				// this will include ".travis.yml"... should we add to "yml" instead?
				is: name => /^\./.test(name),
				text: "\u00ABdot-files\u00BB"
			},
			":min": {
				is: name => /\.min\./.test(name),
				text: "\u00ABmin\u00BB"
			}
		},
		// TODO: add toggle for submodule and dot-folders
		folderIconClasses = [
			".octicon-file-directory",
			".octicon-file-symlink-directory",
			".octicon-file-submodule"
		].join(",");

	// default to all file types visible; remember settings between sessions
	list[":all"] = true; // list gets cleared in buildList function
	settings = GM_getValue("gff-filter-settings", list);

	function updateFilter(event) {
		event.preventDefault();
		event.stopPropagation();
		const el = event.target;
		toggleBlocks(
			el.getAttribute("data-ext"),
			el.classList.contains("selected") ? "hide" : "show"
		);
	}

	function updateSettings(name, mode) {
		settings[name] = mode === "show";
		GM_setValue("gff-filter-settings", settings);
	}

	function updateAllButton() {
		if ($(".gff-filter")) {
			const buttons = $(".file-wrap .gff-filter"),
				filters = $$(".btn:not(.gff-all)", buttons),
				selected = $$(".btn:not(.gff-all).selected", buttons);
			// set "all" button
			$(".gff-all", buttons).classList.toggle(
				"selected",
				filters.length === selected.length
			);
		}
	}

	function toggleImagePreview(ext, mode) {
		if ($(".ghip-image-previews")) {
			let selector = "a",
				hasType = types[ext];
			if (!hasType) {
				selector += `[href$="${ext}"]`;
			}
			$$(`.ghip-image-previews ${selector}`).forEach(el => {
				if (!$(".ghip-folder, .ghip-up-tree", el)) {
					if (hasType && ext !== ":all") {
						// image preview includes the filename
						let elm = $(".ghip-file-name", el);
						if (elm && !hasType.is(elm.textContent)) {
							return;
						}
					}
					el.style.display = mode === "show" ? "" : "none";
				}
			});
		}
	}

	function toggleRow(el, mode) {
		const row = el.closest("tr.js-navigation-item");
		// don't toggle folders
		if (row && !$(folderIconClasses, row)) {
			row.style.display = mode === "show" ? "" : "none";
		}
	}

	function toggleAll(mode) {
		const files = $(".file-wrap");
		// Toggle "all" blocks
		$$("td.content .js-navigation-open", files).forEach(el => {
			toggleRow(el, mode);
		});
		// update filter buttons
		$$(".gff-filter .btn", files).forEach(el => {
			el.classList.toggle("selected", mode === "show");
		});
		updateSettings(":all", mode);
	}

	function toggleFilter(filter, mode) {
		const files = $(".file-wrap"),
			elm = $(`.gff-filter .btn[data-ext="${filter}"]`, files);
		/* list[filter] contains an array of file names */
		list[filter].forEach(name => {
			const el = $(`a[title="${name}"]`, files);
			if (el) {
				toggleRow(el, mode);
			}
		});
		if (elm) {
			elm.classList.toggle("selected", mode === "show");
		}
		updateSettings(filter, mode);
	}

	function toggleBlocks(filter, mode) {
		if (filter === ":all") {
			toggleAll(mode);
		} else if (list[filter]) {
			toggleFilter(filter, mode);
		}
		// update view for github-image-preview.user.js
		toggleImagePreview(filter, mode);
		updateAllButton();
	}

	function addExt(ext, txt) {
		if (ext) {
			if (!list[ext]) {
				list[ext] = [];
			}
			list[ext].push(txt);
		}
	}

	function buildList() {
		list = {};
		Object.keys(types).forEach(item => {
			if (item !== ":all") {
				list[item] = [];
			}
		});
		// get all files
		$$("table.files tr.js-navigation-item").forEach(file => {
			if ($("td.icon .octicon-file", file)) {
				let ext, parts, sub,
					link = $("td.content .js-navigation-open", file),
					txt = (link.title || link.textContent || "").trim().toLowerCase(),
					name = txt.split("/").slice(-1)[0];
				// test extension types; fallback to regex extraction
				ext = Object.keys(types).find(item => {
					return types[item].is(name);
				}) || /[^./\\]*$/.exec(name)[0];
				parts = name.split(".");
				if (!ext.startsWith(":") && parts.length > 2 && parts[0] !== "") {
					sub = parts.slice(0, -1).join(".");
					// Prevent version numbers & "vs. " from adding a filter button
					// See https://github.com/tpn/pdfs
					if (!/[()]/.test(sub) && !/[\b\w]\.[\b\d]/.test(sub)) {
						addExt(ext, txt);
						ext = parts.slice(-2).join(".");
					}
				}
				addExt(ext, txt);
			}
		});
	}

	function sortList() {
		return Object.keys(list).sort((a, b) => {
			// move ":" filters to the beginning, then sort the rest of the
			// extensions; test on https://github.com/rbsec/sslscan, where
			// the ".1" extension *was* appearing between ":" filters
			if (a[0] === ":") {
				return -1;
			}
			if (b[0] === ":") {
				return 1;
			}
			return a > b;
		});
	}

	function makeFilter() {
		let filters = 0;
		// get length, but don't count empty arrays
		Object.keys(list).forEach(ext => {
			filters += list[ext].length > 0 ? 1 : 0;
		});
		// Don't bother if only one extension is found
		const files = $(".file-wrap");
		if (files && filters > 1) {
			filters = $(".gff-filter-wrapper");
			if (!filters) {
				filters = document.createElement("div");
				// "commitinfo" allows GitHub-Dark styling
				filters.className = "gff-filter-wrapper commitinfo";
				filters.style = "padding:3px 5px 2px;border-bottom:1px solid #eaecef";
				files.insertBefore(filters, files.firstChild);
				filters.addEventListener("click", updateFilter);
			}
			fixWidth();
			buildHTML();
			applyInitSettings();
		}
	}

	function buildButton(name, label, ext, text) {
		return `<button type="button" ` +
			`class="btn btn-sm selected BtnGroup-item tooltipped tooltipped-n` +
			(name ? name : "") + `" ` +
			`data-ext="${ext}" aria-label="${label}">${text}</button>`;
	}

	function buildHTML() {
		let len,
			html = `<div class="BtnGroup gff-filter">` +
				// add a filter "all" button to the beginning
				buildButton(" gff-all", "Toggle all files", ":all", types[":all"].text);
		sortList().forEach(ext => {
			len = list[ext].length;
			if (len) {
				html += buildButton("", len, ext, types[ext] && types[ext].text || ext);
			}
		});
		// prepend filter buttons
		$(".gff-filter-wrapper").innerHTML = html + "
";
	}

	function getWidth(el) {
		return parseFloat(window.getComputedStyle(el).width);
	}

	// lock-in the table cell widths, or the navigation up link jumps when you
	// hide all files... using percentages in case someone is using GitHub wide
	function fixWidth() {
		let group, width,
			html = "",
			table = $("table.files"),
			tableWidth = getWidth(table),
			cells = $$("tbody:last-child tr:last-child td", table);
		if (table && cells.length > 1 && !$("colgroup", table)) {
			group = document.createElement("colgroup");
			table.insertBefore(group, table.childNodes[0]);
			cells.forEach(el => {
				// keep two decimal point accuracy
				width = parseInt(getWidth(el) / tableWidth * 1e4, 10) / 100;
				html += `<col style="width:${width}%">`;
			});
			group.innerHTML = html;
		}
	}

	function applyInitSettings() {
		// list doesn't include type.all entry
		if (settings[":all"] === false) {
			toggleBlocks(":all", "hide");
		} else {
			Object.keys(list).forEach(name => {
				if (settings[name] === false) {
					toggleBlocks(name, "hide");
				}
			});
		}
	}

	function init() {
		if ($("table.files")) {
			buildList();
			makeFilter();
		}
	}

	function $(str, el) {
		return (el || document).querySelector(str);
	}

	function $$(str, el) {
		return Array.from((el || document).querySelectorAll(str));
	}

	document.addEventListener("ghmo:container", () => {
		// init after a short delay to allow rendering of file list
		setTimeout(() => {
			init();
		}, 200);
	});
	init();

})();
<|endoftext|>


<commit_before>mini.Module(
	"engine/map/entity"
)
.requires(
	"engine/rendering/animationsheet",
	"engine/rendering/animation",
	"engine/time/tween"
)
.defines(function(
	AnimationSheet,
	Animation,
	Tween
) {
	var Entity = mini.Class.subclass({
		animationSheet: new AnimationSheet("sample.png", 96, 128),
		initialize: function() {
			this.animation = new Animation(this.animationSheet, 0.2, [0,1,2,3,2,1]);
			this.aabb = new Jello.AABB(
				new Jello.Vector2(-10, -10),
				new Jello.Vector2( 10,  10)
			);
			this.tween = new Tween(this.aabb.Min)
				.to({"x": 5, "y": -5}, 2.5, Tween.Ease.linear())
				.to({"y": -55}, 5.5, Tween.Ease.linear())
				.onFinished(function() { console.log(1234567890); })
				.start();
		},
		update: function(timePassed) {
			this.animation.update(timePassed);
		},
		draw: function() {

		},
		debugDraw: function(debugDraw) {
			this.animation.draw(this.aabb);
		}
	});

	return Entity;
});
<commit_msg>added wait tween<commit_after>mini.Module(
	"engine/map/entity"
)
.requires(
	"engine/rendering/animationsheet",
	"engine/rendering/animation",
	"engine/time/tween"
)
.defines(function(
	AnimationSheet,
	Animation,
	Tween
) {
	var Entity = mini.Class.subclass({
		animationSheet: new AnimationSheet("sample.png", 96, 128),
		initialize: function() {
			this.animation = new Animation(this.animationSheet, 0.2, [0,1,2,3,2,1]);
			this.aabb = new Jello.AABB(
				new Jello.Vector2(-10, -10),
				new Jello.Vector2( 10,  10)
			);
			this.tween = new Tween(this.aabb.Min)
				.to({"x": 5, "y": -5}, 2.5, Tween.Ease.linear())
				.wait(1.0)
				.to({"y": -55}, 5.5, Tween.Ease.linear())
				.onFinished(function() { console.log(1234567890); })
				.start();
		},
		update: function(timePassed) {
			this.animation.update(timePassed);
		},
		draw: function() {

		},
		debugDraw: function(debugDraw) {
			this.animation.draw(this.aabb);
		}
	});

	return Entity;
});
<|endoftext|>


<commit_before>/**
 * 同时用于virtualGroup和virtualList特性的虚拟列表
 *
 * Created by GUY on 2017/5/22.
 * @class BI.VirtualList
 * @extends BI.Widget
 */
BI.VirtualGroupList = BI.inherit(BI.Widget, {
    props: function () {
        return {
            baseCls: "bi-virtual-group-list",
            overscanHeight: 100,
            blockSize: 10,
            scrollTop: 0,
            rowHeight: "auto",
            items: []
        };
    },

    init: function () {
        var self = this;
        this.renderedIndex = -1;
    },

    render: function () {
        var self = this, o = this.options;
        return {
            type: "bi.vertical",
            items: [{
                type: "bi.layout",
                ref: function () {
                    self.topBlank = this;
                }
            }, {
                type: "bi.virtual_group",
                height: o.rowHeight * o.items.length,
                ref: function () {
                    self.container = this;
                },
                layouts: [{
                    type: "bi.vertical",
                    scrolly: false
                }]
            }, {
                type: "bi.layout",
                ref: function () {
                    self.bottomBlank = this;
                }
            }],
            element: this
        };
    },

    // mounted之后绑定事件
    mounted: function () {
        var self = this, o = this.options;
        this._populate();
        this.element.scroll(function (e) {
            o.scrollTop = self.element.scrollTop();
            self._calculateBlocksToRender();
        });
        BI.ResizeDetector.addResizeListener(this, function () {
            self._calculateBlocksToRender();
        });
    },

    _isAutoHeight: function () {
        return this.options.rowHeight === "auto";
    },

    _renderMoreIf: function () {
        var self = this, o = this.options;
        var height = this.element.height();
        var minContentHeight = o.scrollTop + height + o.overscanHeight;
        var index = (this.renderedIndex + 1) * o.blockSize, cnt = this.renderedIndex + 1;
        var lastHeight;
        var getElementHeight = function () {
            return self.container.element.height() + self.topBlank.element.height() + self.bottomBlank.element.height();
        };
        while ((lastHeight = getElementHeight()) < minContentHeight && index < o.items.length) {
            var items = o.items.slice(index, index + o.blockSize);
            this.container.addItems(items, this);
            var addedHeight = getElementHeight() - lastHeight;
            this.tree.set(cnt, addedHeight);
            this.renderedIndex = cnt;
            cnt++;
            index += o.blockSize;
        }
    },

    _calculateBlocksToRender: function () {
        var o = this.options;
        this._isAutoHeight() && this._renderMoreIf();
        var height = this.element.height();
        var minContentHeightFrom = o.scrollTop - o.overscanHeight;
        var minContentHeightTo = o.scrollTop + height + o.overscanHeight;
        var start = this.tree.greatestLowerBound(minContentHeightFrom);
        var end = this.tree.leastUpperBound(minContentHeightTo);
        var items = [];
        var topHeight = this.tree.sumTo(Math.max(-1, start - 1));
        this.topBlank.setHeight(topHeight);
        if (this._isAutoHeight()) {
            for (var i = (start < 0 ? 0 : start); i <= end && i <= this.renderedIndex; i++) {
                var index = i * o.blockSize;
                for (var j = index; j < index + o.blockSize && j < o.items.length; j++) {
                    items.push(o.items[j]);
                }
            }
            this.bottomBlank.setHeight(this.tree.sumTo(this.renderedIndex) - this.tree.sumTo(Math.min(end, this.renderedIndex)));
            this.container.populate(items);
        } else {
            for (var i = (start < 0 ? 0 : start); i <= end; i++) {
                var index = i * o.blockSize;
                for (var j = index; j < index + o.blockSize && j < o.items.length; j++) {
                    items.push(o.items[j]);
                }
            }
            this.container.element.height(o.rowHeight * o.items.length - topHeight);
            this.container.populate(items);
        }
    },

    _populate: function (items) {
        var o = this.options;
        if (items && this.options.items !== items) {
            this.options.items = items;
        }
        this.tree = BI.PrefixIntervalTree.uniform(Math.ceil(o.items.length / o.blockSize), this._isAutoHeight() ? 0 : o.rowHeight * o.blockSize);

        this._calculateBlocksToRender();
        try {
            this.element.scrollTop(o.scrollTop);
        } catch (e) {
        }
    },

    restore: function () {
        this.renderedIndex = -1;
        this.options.scrollTop = 0;
        // 依赖于cache的占位元素也要初始化
        this.topBlank.setHeight(0);
        this.bottomBlank.setHeight(0);
    },

    populate: function (items) {
        this._populate(items);
    }
});
BI.shortcut("bi.virtual_group_list", BI.VirtualGroupList);

<commit_msg>Pull request #2256: 无JIRA任务 virtualgrouplist滚动跳动<commit_after>/**
 * 同时用于virtualGroup和virtualList特性的虚拟列表
 *
 * Created by GUY on 2017/5/22.
 * @class BI.VirtualList
 * @extends BI.Widget
 */
BI.VirtualGroupList = BI.inherit(BI.Widget, {
    props: function () {
        return {
            baseCls: "bi-virtual-group-list",
            overscanHeight: 100,
            blockSize: 10,
            scrollTop: 0,
            rowHeight: "auto",
            items: []
        };
    },

    init: function () {
        var self = this;
        this.renderedIndex = -1;
    },

    render: function () {
        var self = this, o = this.options;
        return {
            type: "bi.vertical",
            items: [{
                type: "bi.layout",
                ref: function () {
                    self.topBlank = this;
                }
            }, {
                type: "bi.virtual_group",
                height: o.rowHeight * o.items.length,
                ref: function () {
                    self.container = this;
                },
                layouts: [{
                    type: "bi.vertical",
                    scrolly: false
                }]
            }, {
                type: "bi.layout",
                ref: function () {
                    self.bottomBlank = this;
                }
            }],
            element: this
        };
    },

    // mounted之后绑定事件
    mounted: function () {
        var self = this, o = this.options;
        this._populate();
        this._debounceRelease = BI.debounce(function () {
            self._scrollLock = false;
        }, 30);
        this.element.scroll(function (e) {
            if (self._scrollLock === true) {
                return;
            }
            self._scrollLock = true;
            o.scrollTop = self.element.scrollTop();
            self._debounceRelease();
            self._calculateBlocksToRender();
        });
        BI.ResizeDetector.addResizeListener(this, function () {
            self._calculateBlocksToRender();
        });
    },

    _isAutoHeight: function () {
        return this.options.rowHeight === "auto";
    },

    _renderMoreIf: function () {
        var self = this, o = this.options;
        var height = this.element.height();
        var minContentHeight = o.scrollTop + height + o.overscanHeight;
        var index = (this.renderedIndex + 1) * o.blockSize, cnt = this.renderedIndex + 1;
        var lastHeight;
        var getElementHeight = function () {
            return self.container.element.height() + self.topBlank.element.height() + self.bottomBlank.element.height();
        };
        while ((lastHeight = getElementHeight()) < minContentHeight && index < o.items.length) {
            var items = o.items.slice(index, index + o.blockSize);
            this.container.addItems(items, this);
            var addedHeight = getElementHeight() - lastHeight;
            this.tree.set(cnt, addedHeight);
            this.renderedIndex = cnt;
            cnt++;
            index += o.blockSize;
        }
    },

    _calculateBlocksToRender: function () {
        var o = this.options;
        this._isAutoHeight() && this._renderMoreIf();
        var height = this.element.height();
        var minContentHeightFrom = o.scrollTop - o.overscanHeight;
        var minContentHeightTo = o.scrollTop + height + o.overscanHeight;
        var start = this.tree.greatestLowerBound(minContentHeightFrom);
        var end = this.tree.leastUpperBound(minContentHeightTo);
        var items = [];
        var topHeight = this.tree.sumTo(Math.max(-1, start - 1));
        this.topBlank.setHeight(topHeight);
        if (this._isAutoHeight()) {
            for (var i = (start < 0 ? 0 : start); i <= end && i <= this.renderedIndex; i++) {
                var index = i * o.blockSize;
                for (var j = index; j < index + o.blockSize && j < o.items.length; j++) {
                    items.push(o.items[j]);
                }
            }
            this.bottomBlank.setHeight(this.tree.sumTo(this.renderedIndex) - this.tree.sumTo(Math.min(end, this.renderedIndex)));
            this.container.populate(items);
        } else {
            for (var i = (start < 0 ? 0 : start); i <= end; i++) {
                var index = i * o.blockSize;
                for (var j = index; j < index + o.blockSize && j < o.items.length; j++) {
                    items.push(o.items[j]);
                }
            }
            this.container.element.height(o.rowHeight * o.items.length - topHeight);
            this.container.populate(items);
        }
    },

    _populate: function (items) {
        var o = this.options;
        if (items && this.options.items !== items) {
            this.options.items = items;
        }
        this.tree = BI.PrefixIntervalTree.uniform(Math.ceil(o.items.length / o.blockSize), this._isAutoHeight() ? 0 : o.rowHeight * o.blockSize);

        this._calculateBlocksToRender();
        try {
            this.element.scrollTop(o.scrollTop);
        } catch (e) {
        }
    },

    restore: function () {
        this.renderedIndex = -1;
        this.options.scrollTop = 0;
        // 依赖于cache的占位元素也要初始化
        this.topBlank.setHeight(0);
        this.bottomBlank.setHeight(0);
    },

    populate: function (items) {
        this._populate(items);
    }
});
BI.shortcut("bi.virtual_group_list", BI.VirtualGroupList);

<|endoftext|>


<commit_before>'use strict';
const symbol = require('../utilities/symbol');
const getCallerFile = require('get-caller-file');
const deprecate = require('../utilities/deprecate');

let experiments = {
  NPM_BLUEPRINTS: symbol('npm-blueprints'),
  BROWSER_TARGETS: symbol('browser-targets'),
  get INSTRUMENTATION() {
    deprecate(`\`experiments.INSTRUMENTATION\` is deprecated now that the feature has landed, use \`instrumentation\` method on your addon instead. Required from: \n${getCallerFile()}`, true);

    return 'instrumentation';
  },
};

Object.freeze(experiments);

module.exports = experiments;
<commit_msg>experiments: Add "MODULE_UNIFICATION" symbol<commit_after>'use strict';
const symbol = require('../utilities/symbol');
const getCallerFile = require('get-caller-file');
const deprecate = require('../utilities/deprecate');

let experiments = {
  NPM_BLUEPRINTS: symbol('npm-blueprints'),
  BROWSER_TARGETS: symbol('browser-targets'),
  MODULE_UNIFICATION: symbol('module-unification'),
  get INSTRUMENTATION() {
    deprecate(`\`experiments.INSTRUMENTATION\` is deprecated now that the feature has landed, use \`instrumentation\` method on your addon instead. Required from: \n${getCallerFile()}`, true);

    return 'instrumentation';
  },
};

Object.freeze(experiments);

module.exports = experiments;
<|endoftext|>


<commit_before>/*
 * This binds the node-validator library to the req object so that
 * the validation / sanitization methods can be called on parameter
 * names rather than the actual strings.
 *
 *
 * 1. To validate parameters, use `req.check(param_name, [err_message])`
 *        e.g. req.check('param1').len(1, 6).isInt();
 *        e.g. req.checkHeader('referer').contains('mydomain.com');
 *
 *    Each call to `check()` will throw an exception by default. To
 *    specify a custom err handler, use `req.onValidationError(errback)`
 *    where errback receives a parameter containing the error message
 *
 * 2. To sanitize parameters, use `req.sanitize(param_name)`
 *        e.g. req.sanitize('large_text').xss();
 *        e.g. req.sanitize('param2').toInt();
 *
 * 3. Done! Access your validated and sanitized paramaters through the
 *    `req.params` object
 */

var Validator = require('validator').Validator,
    Filter = require('validator').Filter;

var validator = new Validator();

var expressValidator = function(options) {
  options = options || {};

  var _options = {};

  _options.errorFormatter = options.errorFormatter || function(param, msg, value) {
    return {
      param : param,
      msg   : msg,
      value : value
    };
  };

  function checkParam(req, getter) {
    return function(param, fail_msg) {

      var value;

      // If param is not an array, then split by dot notation
      // returning an array. It will return an array even if
      // param doesn't have the dot notation.
      //      'blogpost' = ['blogpost']
      //      'login.username' = ['login', 'username']
      // For regex matches you can access the parameters using numbers.
      if (!Array.isArray(param)) {
        param = typeof param === 'number' ?
                [param] :
                param.split('.').filter(function(e){
                  return e !== '';
                });
      }

      // Extract value from params
      param.map(function(item) {
          if (value === undefined) {
            value = getter(item)
          } else {
            value = value[item];
          }
      });
      param = param.join('.');

      validator.error = function(msg) {
        var error = _options.errorFormatter(param, msg, value);

        if (req._validationErrors === undefined) {
          req._validationErrors = [];
        }
        req._validationErrors.push(error);

        if (req.onErrorCallback) {
          req.onErrorCallback(msg);
        }
        return this;
      }
      return validator.check(value, fail_msg);
    }
  }
  return function(req, res, next) {

    req.updateParam = function(name, value) {
      // route params like /user/:id
      if (this.params && this.params.hasOwnProperty(name) &&
          undefined !== this.params[name]) {
        return this.params[name] = value;
      }
      // query string params
      if (undefined !== this.query[name]) {
        return this.query[name] = value;
      }
      // request body params via connect.bodyParser
      if (this.body && undefined !== this.body[name]) {
        return this.body[name] = value;
      }
      return false;
    };

    req.check = checkParam(req, function(item) {
      return req.param(item);
    });

    req.checkBody = checkParam(req, function(item) {
      return req.body && req.body[item];
    });

    req.checkParams = checkParam(req, function(item) {
      return req.params && req.params[item];
    });

    req.checkQuery = checkParam(req, function(item) {
      return req.query && req.query[item];
    });

    req.checkHeader = function(header, fail_msg) {
      var to_check;
      if (header === 'referrer' || header === 'referer') {
        to_check = this.headers.referer;
      } else {
        to_check = this.headers[header];
      }
      return validator.check(to_check || '', fail_msg);
    };

    req.onValidationError = function(errback) {
      req.onErrorCallback = errback;
    };

    req.validationErrors = function(mapped) {
      if (req._validationErrors === undefined) {
        return null;
      }
      if (mapped) {
        var errors = {};
        req._validationErrors.forEach(function(err) {
          errors[err.param] = err;
        });
        return errors;
      }
      return req._validationErrors;
    }

    req.filter = function(param) {
      var self = this;
      var filter = new Filter();
      filter.modify = function(str) {
        this.str = str;
        // Replace the param with the filtered version
        self.updateParam(param, str);
      };
      return filter.sanitize(this.param(param));
    };

    // Create some aliases - might help with code readability
    req.sanitize = req.filter;
    req.assert = req.check;
    req.validate = req.check;

    return next();
  };
}
module.exports = expressValidator;
module.exports.Validator = Validator;
module.exports.Filter = Filter;
<commit_msg>As .xss() is removed from node-validator 2.0.0, changed example in comments<commit_after>/*
 * This binds the node-validator library to the req object so that
 * the validation / sanitization methods can be called on parameter
 * names rather than the actual strings.
 *
 *
 * 1. To validate parameters, use `req.check(param_name, [err_message])`
 *        e.g. req.check('param1').len(1, 6).isInt();
 *        e.g. req.checkHeader('referer').contains('mydomain.com');
 *
 *    Each call to `check()` will throw an exception by default. To
 *    specify a custom err handler, use `req.onValidationError(errback)`
 *    where errback receives a parameter containing the error message
 *
 * 2. To sanitize parameters, use `req.sanitize(param_name)`
 *        e.g. req.sanitize('param1').toBoolean();
 *        e.g. req.sanitize('param2').toInt();
 *
 * 3. Done! Access your validated and sanitized paramaters through the
 *    `req.params` object
 */

var Validator = require('validator').Validator,
    Filter = require('validator').Filter;

var validator = new Validator();

var expressValidator = function(options) {
  options = options || {};

  var _options = {};

  _options.errorFormatter = options.errorFormatter || function(param, msg, value) {
    return {
      param : param,
      msg   : msg,
      value : value
    };
  };

  function checkParam(req, getter) {
    return function(param, fail_msg) {

      var value;

      // If param is not an array, then split by dot notation
      // returning an array. It will return an array even if
      // param doesn't have the dot notation.
      //      'blogpost' = ['blogpost']
      //      'login.username' = ['login', 'username']
      // For regex matches you can access the parameters using numbers.
      if (!Array.isArray(param)) {
        param = typeof param === 'number' ?
                [param] :
                param.split('.').filter(function(e){
                  return e !== '';
                });
      }

      // Extract value from params
      param.map(function(item) {
          if (value === undefined) {
            value = getter(item)
          } else {
            value = value[item];
          }
      });
      param = param.join('.');

      validator.error = function(msg) {
        var error = _options.errorFormatter(param, msg, value);

        if (req._validationErrors === undefined) {
          req._validationErrors = [];
        }
        req._validationErrors.push(error);

        if (req.onErrorCallback) {
          req.onErrorCallback(msg);
        }
        return this;
      }
      return validator.check(value, fail_msg);
    }
  }
  return function(req, res, next) {

    req.updateParam = function(name, value) {
      // route params like /user/:id
      if (this.params && this.params.hasOwnProperty(name) &&
          undefined !== this.params[name]) {
        return this.params[name] = value;
      }
      // query string params
      if (undefined !== this.query[name]) {
        return this.query[name] = value;
      }
      // request body params via connect.bodyParser
      if (this.body && undefined !== this.body[name]) {
        return this.body[name] = value;
      }
      return false;
    };

    req.check = checkParam(req, function(item) {
      return req.param(item);
    });

    req.checkBody = checkParam(req, function(item) {
      return req.body && req.body[item];
    });

    req.checkParams = checkParam(req, function(item) {
      return req.params && req.params[item];
    });

    req.checkQuery = checkParam(req, function(item) {
      return req.query && req.query[item];
    });

    req.checkHeader = function(header, fail_msg) {
      var to_check;
      if (header === 'referrer' || header === 'referer') {
        to_check = this.headers.referer;
      } else {
        to_check = this.headers[header];
      }
      return validator.check(to_check || '', fail_msg);
    };

    req.onValidationError = function(errback) {
      req.onErrorCallback = errback;
    };

    req.validationErrors = function(mapped) {
      if (req._validationErrors === undefined) {
        return null;
      }
      if (mapped) {
        var errors = {};
        req._validationErrors.forEach(function(err) {
          errors[err.param] = err;
        });
        return errors;
      }
      return req._validationErrors;
    }

    req.filter = function(param) {
      var self = this;
      var filter = new Filter();
      filter.modify = function(str) {
        this.str = str;
        // Replace the param with the filtered version
        self.updateParam(param, str);
      };
      return filter.sanitize(this.param(param));
    };

    // Create some aliases - might help with code readability
    req.sanitize = req.filter;
    req.assert = req.check;
    req.validate = req.check;

    return next();
  };
}
module.exports = expressValidator;
module.exports.Validator = Validator;
module.exports.Filter = Filter;
<|endoftext|>


<commit_before>/*
 * This binds the node-validator library to the req object so that
 * the validation / sanitization methods can be called on parameter
 * names rather than the actual strings.
 *
 * 1. Be sure to include `req.mixinParams()` as middleware to merge
 *    query string, body and named parameters into `req.params`
 *
 * 2. To validate parameters, use `req.check(param_name, [err_message])`
 *        e.g. req.check('param1').len(1, 6).isInt();
 *        e.g. req.checkHeader('referer').contains('mydomain.com');
 *
 *    Each call to `check()` will throw an exception by default. To
 *    specify a custom err handler, use `req.onValidationError(errback)`
 *    where errback receives a parameter containing the error message
 *
 * 3. To sanitize parameters, use `req.sanitize(param_name)`
 *        e.g. req.sanitize('large_text').xss();
 *        e.g. req.sanitize('param2').toInt();
 *
 * 4. Done! Access your validated and sanitized paramaters through the
 *    `req.params` object
 */

var Validator = require('validator').Validator,
    Filter = require('validator').Filter;

var validator = new Validator();

var expressValidator = function(req, res, next) {

  req.updateParam = function(name, value) {
    // route params like /user/:id
    if (this.params && this.params.hasOwnProperty(name) && undefined !== this.params[name]) {
      return this.params[name] = value;
    }
    // query string params
    if (undefined !== this.query[name]) {
      return this.query[name] = value;
    }
    // request body params via connect.bodyParser
    if (this.body && undefined !== this.body[name]) {
      return this.body[name] = value;
    }
    return false;
  };

  req.check = function(param, fail_msg) {
    return validator.check(this.param(param), fail_msg);
  };

  req.checkHeader = function(param, fail_msg) {
    var to_check;
    if (header === 'referrer' || header === 'referer') {
      to_check = this.headers.referer;
    } else {
      to_check = this.headers[header];
    }
    return validator.check(to_check || '', fail_msg);
  };

  req.onValidationError = function(errback) {
    validator.error = errback;
  };

  req.filter = function(param) {
    var self = this;
    var filter = new Filter();
    filter.modify = function(str) {
      this.str = str;
      self.updateParam(param, str); // Replace the param with the filtered version
    };
    return filter.sanitize(this.param(param));
  };

  // Create some aliases - might help with code readability
  req.sanitize = req.filter;
  req.assert = req.check;

  return next();
};
module.exports = expressValidator;
<commit_msg>exposed Validator & Filter to module so that end-users can easily hang their own validations/sanitizers onto them<commit_after>/*
 * This binds the node-validator library to the req object so that
 * the validation / sanitization methods can be called on parameter
 * names rather than the actual strings.
 *
 * 1. Be sure to include `req.mixinParams()` as middleware to merge
 *    query string, body and named parameters into `req.params`
 *
 * 2. To validate parameters, use `req.check(param_name, [err_message])`
 *        e.g. req.check('param1').len(1, 6).isInt();
 *        e.g. req.checkHeader('referer').contains('mydomain.com');
 *
 *    Each call to `check()` will throw an exception by default. To
 *    specify a custom err handler, use `req.onValidationError(errback)`
 *    where errback receives a parameter containing the error message
 *
 * 3. To sanitize parameters, use `req.sanitize(param_name)`
 *        e.g. req.sanitize('large_text').xss();
 *        e.g. req.sanitize('param2').toInt();
 *
 * 4. Done! Access your validated and sanitized paramaters through the
 *    `req.params` object
 */

var Validator = require('validator').Validator,
    Filter = require('validator').Filter;

var validator = new Validator();

var expressValidator = function(req, res, next) {

  req.updateParam = function(name, value) {
    // route params like /user/:id
    if (this.params && this.params.hasOwnProperty(name) && undefined !== this.params[name]) {
      return this.params[name] = value;
    }
    // query string params
    if (undefined !== this.query[name]) {
      return this.query[name] = value;
    }
    // request body params via connect.bodyParser
    if (this.body && undefined !== this.body[name]) {
      return this.body[name] = value;
    }
    return false;
  };

  req.check = function(param, fail_msg) {
    return validator.check(this.param(param), fail_msg);
  };

  req.checkHeader = function(param, fail_msg) {
    var to_check;
    if (header === 'referrer' || header === 'referer') {
      to_check = this.headers.referer;
    } else {
      to_check = this.headers[header];
    }
    return validator.check(to_check || '', fail_msg);
  };

  req.onValidationError = function(errback) {
    validator.error = errback;
  };

  req.filter = function(param) {
    var self = this;
    var filter = new Filter();
    filter.modify = function(str) {
      this.str = str;
      self.updateParam(param, str); // Replace the param with the filtered version
    };
    return filter.sanitize(this.param(param));
  };

  // Create some aliases - might help with code readability
  req.sanitize = req.filter;
  req.assert = req.check;

  return next();
};
module.exports = expressValidator;
module.exports.Validator = Validator;
module.exports.Filter = Filter;
<|endoftext|>


<commit_before>const helper = require('think-helper');
const debug = require('debug')('thinkjs');
/**
 * extend controller
 */
module.exports = {
  /**
   * body getter
   */
  get body() {
    return this.ctx.body;
  },
  /**
   * body setter
   */
  set body(value) {
    this.ctx.body = value;
  },
  /**
   * get client ip
   */
  get ip() {
    return this.ctx.ip;
  },
  /**
   * get client ips
   */
  get ips() {
    return this.ctx.ips;
  },
  /**
   * get status code
   */
  get status() {
    return this.ctx.status;
  },
  /**
   * set status code
   */
  set status(status) {
    this.ctx.status = status;
  },
  /**
   * get content type
   */
  get type() {
    return this.ctx.type;
  },
  /**
   * set content type
   */
  set type(contentType) {
    this.ctx.type = contentType;
  },
  /**
   * get or set config
   * @param {String} name
   * @param {Mix} value
   * @param {String} m
   */
  config(name, value, m = this.ctx.module) {
    return think.config(name, value, m);
  },
  /**
   * get request method
   */
  get method() {
    return this.ctx.method;
  },
  /**
   * is method
   * @param {String} method
   */
  isMethod(method) {
    return this.ctx.isMethod(method);
  },
  /**
   * is get method
   */
  get isGet() {
    return this.ctx.isGet;
  },
  /**
   * is post method
   */
  get isPost() {
    return this.ctx.isPost;
  },
  /**
   * is command line invoke
   */
  get isCli() {
    return think.isCli;
  },
  /**
   * check if is ajax request
   * @param {String} method
   */
  isAjax(method) {
    return this.ctx.isAjax(method);
  },
  /**
   * is jsonp request
   * @param {String} callback
   */
  isJsonp(callbackField) {
    return this.ctx.isJsonp(callbackField);
  },
  /**
   * send jsonp data
   */
  jsonp(data, callbackField) {
    return this.ctx.jsonp(data, callbackField);
  },
  /**
   * send json data
   */
  json(data) {
    return this.ctx.json(data);
  },
  /**
   * send success data
   */
  success(data, message) {
    return this.ctx.success(data, message);
  },
  /**
   * send fail data
   */
  fail(errno, errmsg, data) {
    return this.ctx.fail(errno, errmsg, data);
  },
  /**
   * set expires header
   * @param {Number} time
   */
  expires(time) {
    return this.ctx.expires(time);
  },
  /**
   * get query data
   * @param {String} name
   * @param {Mixed} value
   */
  get(name, value) {
    return this.ctx.param(name, value);
  },
  /**
   * get query data
   * @param {String} name
   * @param {Mixed} value
   */
  query(name, value) {
    return this.ctx.param(name, value);
  },
  /**
   * get or set post data
   * @param {String} name
   * @param {Mixed} value
   */
  post(name, value) {
    return this.ctx.post(name, value);
  },
  /**
   * get or set file data
   * @param {String} name
   * @param {Mixed} value
   */
  file(name, value) {
    return this.ctx.file(name, value);
  },
  /**
   * get or set cookies
   * @param {String} name
   * @param {String} value
   * @param {Object} options
   */
  cookie(name, value, options) {
    return this.ctx.cookie(name, value, options);
  },
  /**
   * get or set header
   * @param {String} name
   * @param {Mixed} value
   */
  header(name, value) {
    if (value === undefined && helper.isString(name)) {
      return this.ctx.header[name];
    }
    if (this.ctx.res.headersSent) {
      debug(`headers has already sent, url: ${this.ctx.url}`);
      return;
    }
    if (value !== undefined) {
      return this.ctx.set(name, value);
    }
    if (helper.isObject(name)) {
      return this.ctx.set(name);
    }
  },
  /**
   * get userAgent header
   */
  get userAgent() {
    return this.ctx.userAgent;
  },
  /**
   * get referer header
   */
  referrer(onlyHost) {
    return this.ctx.referer(onlyHost);
  },
  /**
   * get referer header
   */
  referer(onlyHost) {
    return this.ctx.referer(onlyHost);
  },
  /**
   * Perform a 302 redirect to `url`.
   * @param {String} url
   * @param {String} alt
   */
  redirect(url, alt) {
    return this.ctx.redirect(url, alt);
  },
  /**
   * get controller instance
   * @param {String} name
   * @param {String} m
   */
  controller(name, m) {
    return think.controller(name, this.ctx, m);
  },
  /**
   * get service
   * @param {String} name
   * @param {String} m
   */
  service(name, m) {
    return think.service(name, m);
  },
  /**
   * execute action
   * @param {String} controller
   * @param {String} actionName
   * @param {String} m
   */
  action(controller, actionName, m) {
    const instance = this.controller(controller, m);
    let promise = Promise.resolve();
    if (instance.__before) {
      promise = Promise.resolve(instance.__before());
    }
    return promise.then(data => {
      if (data === false) return false;
      let method = `${actionName}Action`;
      if (!instance[method]) {
        method = '__call';
      }
      if (instance[method]) {
        return instance[method]();
      }
    }).then(data => {
      if (data === false) return false;
      if (instance.__after) {
        instance.__after();
      }
      return data;
    });
  }
};
<commit_msg>Return __after value in action method<commit_after>const helper = require('think-helper');
const debug = require('debug')('thinkjs');
/**
 * extend controller
 */
module.exports = {
  /**
   * body getter
   */
  get body() {
    return this.ctx.body;
  },
  /**
   * body setter
   */
  set body(value) {
    this.ctx.body = value;
  },
  /**
   * get client ip
   */
  get ip() {
    return this.ctx.ip;
  },
  /**
   * get client ips
   */
  get ips() {
    return this.ctx.ips;
  },
  /**
   * get status code
   */
  get status() {
    return this.ctx.status;
  },
  /**
   * set status code
   */
  set status(status) {
    this.ctx.status = status;
  },
  /**
   * get content type
   */
  get type() {
    return this.ctx.type;
  },
  /**
   * set content type
   */
  set type(contentType) {
    this.ctx.type = contentType;
  },
  /**
   * get or set config
   * @param {String} name
   * @param {Mix} value
   * @param {String} m
   */
  config(name, value, m = this.ctx.module) {
    return think.config(name, value, m);
  },
  /**
   * get request method
   */
  get method() {
    return this.ctx.method;
  },
  /**
   * is method
   * @param {String} method
   */
  isMethod(method) {
    return this.ctx.isMethod(method);
  },
  /**
   * is get method
   */
  get isGet() {
    return this.ctx.isGet;
  },
  /**
   * is post method
   */
  get isPost() {
    return this.ctx.isPost;
  },
  /**
   * is command line invoke
   */
  get isCli() {
    return think.isCli;
  },
  /**
   * check if is ajax request
   * @param {String} method
   */
  isAjax(method) {
    return this.ctx.isAjax(method);
  },
  /**
   * is jsonp request
   * @param {String} callback
   */
  isJsonp(callbackField) {
    return this.ctx.isJsonp(callbackField);
  },
  /**
   * send jsonp data
   */
  jsonp(data, callbackField) {
    return this.ctx.jsonp(data, callbackField);
  },
  /**
   * send json data
   */
  json(data) {
    return this.ctx.json(data);
  },
  /**
   * send success data
   */
  success(data, message) {
    return this.ctx.success(data, message);
  },
  /**
   * send fail data
   */
  fail(errno, errmsg, data) {
    return this.ctx.fail(errno, errmsg, data);
  },
  /**
   * set expires header
   * @param {Number} time
   */
  expires(time) {
    return this.ctx.expires(time);
  },
  /**
   * get query data
   * @param {String} name
   * @param {Mixed} value
   */
  get(name, value) {
    return this.ctx.param(name, value);
  },
  /**
   * get query data
   * @param {String} name
   * @param {Mixed} value
   */
  query(name, value) {
    return this.ctx.param(name, value);
  },
  /**
   * get or set post data
   * @param {String} name
   * @param {Mixed} value
   */
  post(name, value) {
    return this.ctx.post(name, value);
  },
  /**
   * get or set file data
   * @param {String} name
   * @param {Mixed} value
   */
  file(name, value) {
    return this.ctx.file(name, value);
  },
  /**
   * get or set cookies
   * @param {String} name
   * @param {String} value
   * @param {Object} options
   */
  cookie(name, value, options) {
    return this.ctx.cookie(name, value, options);
  },
  /**
   * get or set header
   * @param {String} name
   * @param {Mixed} value
   */
  header(name, value) {
    if (value === undefined && helper.isString(name)) {
      return this.ctx.header[name];
    }
    if (this.ctx.res.headersSent) {
      debug(`headers has already sent, url: ${this.ctx.url}`);
      return;
    }
    if (value !== undefined) {
      return this.ctx.set(name, value);
    }
    if (helper.isObject(name)) {
      return this.ctx.set(name);
    }
  },
  /**
   * get userAgent header
   */
  get userAgent() {
    return this.ctx.userAgent;
  },
  /**
   * get referer header
   */
  referrer(onlyHost) {
    return this.ctx.referer(onlyHost);
  },
  /**
   * get referer header
   */
  referer(onlyHost) {
    return this.ctx.referer(onlyHost);
  },
  /**
   * Perform a 302 redirect to `url`.
   * @param {String} url
   * @param {String} alt
   */
  redirect(url, alt) {
    return this.ctx.redirect(url, alt);
  },
  /**
   * get controller instance
   * @param {String} name
   * @param {String} m
   */
  controller(name, m) {
    return think.controller(name, this.ctx, m);
  },
  /**
   * get service
   * @param {String} name
   * @param {String} m
   */
  service(name, m) {
    return think.service(name, m);
  },
  /**
   * execute action
   * @param {String} controller
   * @param {String} actionName
   * @param {String} m
   */
  action(controller, actionName, m) {
    const instance = this.controller(controller, m);
    let promise = Promise.resolve();
    if (instance.__before) {
      promise = Promise.resolve(instance.__before());
    }
    return promise.then(data => {
      if (data === false) return false;
      let method = `${actionName}Action`;
      if (!instance[method]) {
        method = '__call';
      }
      if (instance[method]) {
        return instance[method]();
      }
    }).then(data => {
      if (data === false) return false;
      if (instance.__after) {
        return instance.__after();
      }
      return data;
    });
  }
};
<|endoftext|>


<commit_before>"use strict";

/**
 * Key-value bucket handler
 */

var RouteSwitch = require('routeswitch');
var uuid = require('node-uuid');
var rbUtil = require('../../util.js');

var backend;
var config;

function KVBucket (options) {
    this.log = options.log || function(){};
}

KVBucket.prototype.getBucketInfo = function(restbase, req, options) {
    var self = this;
    return Promise.resolve({
        status: 200,
        body: options
    });
};

KVBucket.prototype.makeSchema = function (opts) {
    if (opts.type === 'kv') {
        opts.schemaVersion = 1;
        return {
            // Associate this bucket with the table
            bucket: opts,
            attributes: {
                key: opts.keyType || 'string',
                tid: 'timeuuid',
                latestTid: 'timeuuid',
                value: opts.valueType || 'blob',
                'content-type': 'string',
                'content-length': 'varint',
                'content-sha256': 'string',
                // redirect
                'content-location': 'string',
                // 'deleted', 'nomove' etc?
                tags: 'set<string>',
            },
            index: {
                hash: 'key',
                range: 'tid',
                order: 'desc',
                static: 'latestTid'
            }
        };
    } else {
        throw new Error('Bucket type ' + opts.type + ' not yet implemented');
    }
};

KVBucket.prototype.createBucket = function(restbase, req) {
    if (!req.body
            || req.body.constructor !== Object
            || req.body.type !== 'kv' )
    {
        // XXX: validate with JSON schema
        var exampleBody = {
            type: 'kv',
            revisioned: true,
            keyType: 'string',
            valueType: 'blob'
        };

        return Promise.resolve({
            status: 400,
            body: {
                type: 'invalid_bucket_schema_kv',
                message: "Expected JSON body describing the bucket.",
                example: exampleBody
            }
        });
    }
    var opts = req.body;
    if (!opts.keyType) { opts.keyType = 'string'; }
    if (!opts.valueType) { opts.valueType = 'blob'; }
    if (!opts.revisioned) { opts.revisioned = true; }
    var schema = this.makeSchema(opts);
    schema.table = req.params.bucket;
    var storeRequest = {
        uri: '/v1/' + req.params.domain + '/' + req.params.bucket,
        body: schema
    };
    return restbase.put(storeRequest);
};

KVBucket.prototype.getListQuery = function (options, bucket) {
    // TODO: support other bucket types
    //if (!options.revisioned || options.ordered) {
    //    throw new Error('Only unordered & revisioned key-value buckets supported to far');
    //}
    return {
        table: bucket,
        distinct: true,
        proj: 'key',
        limit: 10000
    };
};

KVBucket.prototype.listBucket = function(restbase, req, options) {
    var self = this;
    // XXX: check params!
    var params = req.params;

    var listQuery = this.getListQuery(options, params.bucket);
    return restbase.get({
        uri: req.uri,
        body: listQuery
    })
    .then(function(result) {
        var listing = result.body.items.map(function(row) {
            return row.key;
        });
        return {
            status: 200,
            headers: {
                'content-type': 'application/json'
            },
            body: listing
        };
    })
    .catch(function(error) {
        self.log('error/kv/listBucket', error);
        return { status: 404 };
    });
};

// Format a revision response. Shared between different ways to retrieve a
// revision (latest & with explicit revision).
KVBucket.prototype.returnRevision = function(req, dbResult) {
    //console.log(req, dbResult);
    if (dbResult.body && dbResult.body.items && dbResult.body.items.length) {
        var row = dbResult.body.items[0];
        var headers = {
            etag: row.tid,
            'content-type': row['content-type']
        };
        return {
            status: 200,
            headers: headers,
            body: row.value
        };
    } else {
        return {
            status: 404,
            body: {
                type: 'not_found',
                uri: req.uri,
                method: req.method
            }
        };
    }
};

KVBucket.prototype.getLatest = function(restbase, req, options) {
    // XXX: check params!
    var query = {
        table: req.params.bucket,
        attributes: {
            key: req.params.key
        },
        limit: 1
    };

    return restbase.get({
        uri: req.uri,
        body: query
    })
    .then(this.returnRevision.bind(this, req))
    .catch(function(error) {
        console.error(error);
        return { status: 404 };
    });
};

KVBucket.prototype.putLatest = function(restbase, req) {
    var self = this;

    var tid = uuid.v1();
    if (req.headers['last-modified']) {
        try {
            // XXX: require elevated rights for passing in the revision time
            tid = rbUtil.tidFromDate(new Date(req.headers['last-modified']));
        } catch (e) { }
    }

    var query = {
        table: req.params.bucket,
        attributes: {
            key: req.params.key,
            tid: tid,
            value: req.body,
            'content-type': req.headers['content-type']
        }
    };
    var request = {
        uri: req.uri,
        body: query
    };

    return restbase.put(request)
    .then(function(result) {
        return {
            status: 201,
            headers: {
                etag: tid
            },
            body: {
                message: "Created.",
                tid: tid
            }
        };
    })
    .catch(function(err) {
        self.log(err.stack);
        return {
            status: 500,
            body: {
                message: "Unknown error\n" + err.stack
            }
        };
    });
};

KVBucket.prototype.listRevisions = function(restbase, req) {
    var storeRequest = {
        uri: req.uri,
        body: {
            table: req.params.bucket,
            attributes: {
                key: req.params.key
            },
            proj: ['tid']
        }
    };
    return restbase.get(storeRequest)
    .then(function(res) {
        return {
            status: 200,
            headers: {
                'content-type': 'application/json'
            },
            body: res.body.items.map(function(row) {
                        return row.tid;
                  })
        };
    });
};

var uuidRe = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
function validateRevision (revString) {
    var rev = null;

    if (revString === 'latest') {
        // latest revision
        rev = revString;
    //} else if (/^\d+$/.test(revString)) {
    //    // oldid
    //    // XXX: move to mw-pagecontent bucket
    //    rev = Number(revString);
    } else if (/^\d{4}-\d{2}-\d{2}/.test(revString)) {
        // timestamp
        rev = new Date(revString);
        if (isNaN(rev.valueOf())) {
            // invalid date
            return Promise.resolve({
                status: 400,
                body: 'Invalid date'
            });
        }
    } else if (uuidRe.test(revString)) {
        // uuid
        rev = revString;
    }
    return rev;
}

KVBucket.prototype.getRevision = function(restbase, req) {
    var rp = req.params;
    // TODO: support other formats! See cassandra backend getRevision impl.
    var rev = validateRevision(req.params.revision);
    if (rev === null) {
        return Promise.resolve({
            status: 400,
            body: {
                type: 'invalid_revision_parameter',
                title: 'Invalid revision parameter.',
                revision: req.params.revision
            }
        });
    }
    var storeReq = {
        uri: req.uri,
        body: {
            table: rp.bucket,
            attributes: {
                key: req.params.key,
                tid: rev
            }
        }
    };
    return restbase.get(storeReq)
    .then(this.returnRevision.bind(this, req));
};

KVBucket.prototype.putRevision = function(restbase, req) {
    // TODO: support other formats! See cassandra backend getRevision impl.
    var rp = req.params;
    var rev = validateRevision(rp.revision);
    if (rev === null) {
        return Promise.resolve({
            status: 400,
            body: {
                type: 'invalid_revision_parameter',
                title: 'Invalid revision parameter.',
                revision: rp.revision
            }
        });
    }
    var storeReq = {
        uri: req.uri,
        body: {
            table: rp.bucket,
            attributes: {
                key: rp.key,
                tid: rev,
                value: req.body,
                'content-type': req.headers['content-type']
                // TODO: include other data!
            }
        }
    };
    return restbase.put(storeReq)
    .then(function(res) {
        if (res.status === 201) {
            return {
                status: 201,
                headers: {
                    etag: rp.revision
                },
                body: {
                    message: "Created.",
                    tid: rp.revision
                }
            };
        } else {
            throw result;
        }
    })
    .catch(function(error) {
        console.error(error);
        return { status: 400 };
    });
};

module.exports = function(options) {
    var revBucket = new KVBucket(options);
    // XXX: add docs
    return {
        paths: {
            '/v1/{domain}/{bucket}': {
                get: { request_handler: revBucket.getBucketInfo.bind(revBucket) },
                put: { request_handler: revBucket.createBucket.bind(revBucket) }
            },
            '/v1/{domain}/{bucket}/': {
                get: { request_handler: revBucket.listBucket.bind(revBucket) }
            },
            '/v1/{domain}/{bucket}/{key}': {
                get: { request_handler: revBucket.getLatest.bind(revBucket) },
                put: { request_handler: revBucket.putLatest.bind(revBucket) }
            },
            '/v1/{domain}/{bucket}/{key}/': {
                get: { request_handler: revBucket.listRevisions.bind(revBucket) },
            },
            '/v1/{domain}/{bucket}/{key}/{revision}': {
                get: { request_handler: revBucket.getRevision.bind(revBucket) },
                put: { request_handler: revBucket.putRevision.bind(revBucket) }
            }
        }
    };
};
<commit_msg>Support property retrieval and saving by timestamp<commit_after>"use strict";

/**
 * Key-value bucket handler
 */

var RouteSwitch = require('routeswitch');
var uuid = require('node-uuid');
var rbUtil = require('../../util.js');

var backend;
var config;

function KVBucket (options) {
    this.log = options.log || function(){};
}

KVBucket.prototype.getBucketInfo = function(restbase, req, options) {
    var self = this;
    return Promise.resolve({
        status: 200,
        body: options
    });
};

KVBucket.prototype.makeSchema = function (opts) {
    if (opts.type === 'kv') {
        opts.schemaVersion = 1;
        return {
            // Associate this bucket with the table
            bucket: opts,
            attributes: {
                key: opts.keyType || 'string',
                tid: 'timeuuid',
                latestTid: 'timeuuid',
                value: opts.valueType || 'blob',
                'content-type': 'string',
                'content-length': 'varint',
                'content-sha256': 'string',
                // redirect
                'content-location': 'string',
                // 'deleted', 'nomove' etc?
                tags: 'set<string>',
            },
            index: {
                hash: 'key',
                range: 'tid',
                order: 'desc',
                static: 'latestTid'
            }
        };
    } else {
        throw new Error('Bucket type ' + opts.type + ' not yet implemented');
    }
};

KVBucket.prototype.createBucket = function(restbase, req) {
    if (!req.body
            || req.body.constructor !== Object
            || req.body.type !== 'kv' )
    {
        // XXX: validate with JSON schema
        var exampleBody = {
            type: 'kv',
            revisioned: true,
            keyType: 'string',
            valueType: 'blob'
        };

        return Promise.resolve({
            status: 400,
            body: {
                type: 'invalid_bucket_schema_kv',
                message: "Expected JSON body describing the bucket.",
                example: exampleBody
            }
        });
    }
    var opts = req.body;
    if (!opts.keyType) { opts.keyType = 'string'; }
    if (!opts.valueType) { opts.valueType = 'blob'; }
    if (!opts.revisioned) { opts.revisioned = true; }
    var schema = this.makeSchema(opts);
    schema.table = req.params.bucket;
    var storeRequest = {
        uri: '/v1/' + req.params.domain + '/' + req.params.bucket,
        body: schema
    };
    return restbase.put(storeRequest);
};

KVBucket.prototype.getListQuery = function (options, bucket) {
    // TODO: support other bucket types
    //if (!options.revisioned || options.ordered) {
    //    throw new Error('Only unordered & revisioned key-value buckets supported to far');
    //}
    return {
        table: bucket,
        distinct: true,
        proj: 'key',
        limit: 10000
    };
};

KVBucket.prototype.listBucket = function(restbase, req, options) {
    var self = this;
    // XXX: check params!
    var params = req.params;

    var listQuery = this.getListQuery(options, params.bucket);
    return restbase.get({
        uri: req.uri,
        body: listQuery
    })
    .then(function(result) {
        var listing = result.body.items.map(function(row) {
            return row.key;
        });
        return {
            status: 200,
            headers: {
                'content-type': 'application/json'
            },
            body: listing
        };
    })
    .catch(function(error) {
        self.log('error/kv/listBucket', error);
        return { status: 404 };
    });
};

// Format a revision response. Shared between different ways to retrieve a
// revision (latest & with explicit revision).
KVBucket.prototype.returnRevision = function(req, dbResult) {
    //console.log(req, dbResult);
    if (dbResult.body && dbResult.body.items && dbResult.body.items.length) {
        var row = dbResult.body.items[0];
        var headers = {
            etag: row.tid,
            'content-type': row['content-type']
        };
        return {
            status: 200,
            headers: headers,
            body: row.value
        };
    } else {
        return {
            status: 404,
            body: {
                type: 'not_found',
                uri: req.uri,
                method: req.method
            }
        };
    }
};

KVBucket.prototype.getLatest = function(restbase, req, options) {
    // XXX: check params!
    var query = {
        table: req.params.bucket,
        attributes: {
            key: req.params.key
        },
        limit: 1
    };

    return restbase.get({
        uri: req.uri,
        body: query
    })
    .then(this.returnRevision.bind(this, req))
    .catch(function(error) {
        console.error(error);
        return { status: 404 };
    });
};

KVBucket.prototype.putLatest = function(restbase, req) {
    var self = this;

    var tid = uuid.v1();
    if (req.headers['last-modified']) {
        try {
            // XXX: require elevated rights for passing in the revision time
            tid = rbUtil.tidFromDate(new Date(req.headers['last-modified']));
        } catch (e) { }
    }

    var query = {
        table: req.params.bucket,
        attributes: {
            key: req.params.key,
            tid: tid,
            value: req.body,
            'content-type': req.headers['content-type']
        }
    };
    var request = {
        uri: req.uri,
        body: query
    };

    return restbase.put(request)
    .then(function(result) {
        return {
            status: 201,
            headers: {
                etag: tid
            },
            body: {
                message: "Created.",
                tid: tid
            }
        };
    })
    .catch(function(err) {
        self.log(err.stack);
        return {
            status: 500,
            body: {
                message: "Unknown error\n" + err.stack
            }
        };
    });
};

KVBucket.prototype.listRevisions = function(restbase, req) {
    var storeRequest = {
        uri: req.uri,
        body: {
            table: req.params.bucket,
            attributes: {
                key: req.params.key
            },
            proj: ['tid']
        }
    };
    return restbase.get(storeRequest)
    .then(function(res) {
        return {
            status: 200,
            headers: {
                'content-type': 'application/json'
            },
            body: res.body.items.map(function(row) {
                        return row.tid;
                  })
        };
    });
};

var uuidRe = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
function getRevisionPredicate (revString) {
    var rev = null;

    if (revString === 'latest') {
        // latest revision
        rev = revString;
    } else if (/^\d{4}-\d{2}-\d{2}/.test(revString)) {
        // timestamp
        var revTime = new Date(revString);
        if (isNaN(revTime.valueOf())) {
            // invalid date
            return Promise.resolve({
                status: 400,
                body: 'Invalid date'
            });
        }
        rev = {
            le: rbUtil.tidFromDate(revTime)
        };
    } else if (uuidRe.test(revString)) {
        // uuid
        rev = revString;
    }
    return rev;
}

KVBucket.prototype.getRevision = function(restbase, req) {
    var rp = req.params;
    // TODO: support other formats! See cassandra backend getRevision impl.
    var revPred = getRevisionPredicate(req.params.revision);
    if (revPred === null) {
        return Promise.resolve({
            status: 400,
            body: {
                type: 'invalid_revision_parameter',
                title: 'Invalid revision parameter.',
                revision: rp.revision
            }
        });
    }
    var storeReq = {
        uri: req.uri,
        body: {
            table: rp.bucket,
            attributes: {
                key: rp.key,
                tid: revPred
            }
        }
    };
    return restbase.get(storeReq)
    .then(this.returnRevision.bind(this, req));
};

KVBucket.prototype.putRevision = function(restbase, req) {
    // TODO: support other formats! See cassandra backend getRevision impl.
    var rp = req.params;
    var rev = getRevisionPredicate(rp.revision);
    if (rev === null) {
        return Promise.resolve({
            status: 400,
            body: {
                type: 'invalid_revision_parameter',
                title: 'Invalid revision parameter.',
                revision: rp.revision
            }
        });
    }

    if (typeof rev === 'object') {
        // XXX: Restrict access to passing in an explicit revision id via a
        // timestamp
        rev = rev.le;
    }

    var storeReq = {
        uri: req.uri,
        body: {
            table: rp.bucket,
            attributes: {
                key: rp.key,
                tid: rev,
                value: req.body,
                'content-type': req.headers['content-type']
                // TODO: include other data!
            }
        }
    };
    return restbase.put(storeReq)
    .then(function(res) {
        if (res.status === 201) {
            return {
                status: 201,
                headers: {
                    etag: rp.revision
                },
                body: {
                    message: "Created.",
                    tid: rp.revision
                }
            };
        } else {
            throw res;
        }
    })
    .catch(function(error) {
        console.error(error);
        return { status: 400 };
    });
};

module.exports = function(options) {
    var revBucket = new KVBucket(options);
    // XXX: add docs
    return {
        paths: {
            '/v1/{domain}/{bucket}': {
                get: { request_handler: revBucket.getBucketInfo.bind(revBucket) },
                put: { request_handler: revBucket.createBucket.bind(revBucket) }
            },
            '/v1/{domain}/{bucket}/': {
                get: { request_handler: revBucket.listBucket.bind(revBucket) }
            },
            '/v1/{domain}/{bucket}/{key}': {
                get: { request_handler: revBucket.getLatest.bind(revBucket) },
                put: { request_handler: revBucket.putLatest.bind(revBucket) }
            },
            '/v1/{domain}/{bucket}/{key}/': {
                get: { request_handler: revBucket.listRevisions.bind(revBucket) },
            },
            '/v1/{domain}/{bucket}/{key}/{revision}': {
                get: { request_handler: revBucket.getRevision.bind(revBucket) },
                put: { request_handler: revBucket.putRevision.bind(revBucket) }
            }
        }
    };
};
<|endoftext|>


<commit_before>'use strict';

var cheerio = require('cheerio');
var by = {
	binding: function(value) {
		return attrSelector('bind', value);
	},
	model: function(value) {
		return attrSelector('model', value);
	},
	repeater: function(value) {
		return attrSelector('repeat', value);
	},
	css: function(value) {
		return value;
	},
	id: function(value) {
		return prefixSelector('#', value);
	},
	className: function(value) {
		return prefixSelector('.', value);
	},
	name: function(value) {
		return attrSelector('name', value);
	}
};

function FindViewMatches(selectors, content) {
	loopSelectors(selectors, content);
}

function loopSelectors(selectors, content) {
	var $ = cheerio.load(content);

	selectors.forEach(function(selector) {
		var selectorString = '';
		var value = selector.value;
		var type = selector.type;

		if (!by[type] && type !== 'binding') {
			selector.disabled = 1;
			return;
		} else {
			selectorString = by[type](value);
		}

		if ($(selectorString).length) {
			selector.found = 1;
		}

		var byBinding = by.binding(value);
		if (type === 'binding' &&
			($(byBinding).length || bindExists(value, content))) {
			selector.found = 1;
		}
	});
}

function attrSelector(attr, value) {
	// 'ng:' excluded, since it's not a `cheerio` valid selector.
	var ngPrefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-'];
	var output = [];

	for (var i = 0; i < ngPrefixes.length; i = i + 1) {
		output.push(
			'[' + ngPrefixes[i] + attr + '="' + value + '"]' +
			(i === (ngPrefixes.length - 1) ? '' : ', ')
		);
	}

	return output.join('');
}

function prefixSelector(prefix, value) {
	return value.indexOf(prefix) === 0 ? value : prefix + value;
}

function bindExists(bind, contents) {
	var results;
	var exists = 0;
	var pattern = /{{(.*?)}}/gi;

	while ((results = pattern.exec(contents)) !== null) {
		if (results[1].indexOf(bind) >= 0) {
			exists = 1;
		}
	}

	return exists;
}

module.exports = FindViewMatches;
<commit_msg>Update find-view-matches.js to support more locators<commit_after>'use strict';

var cheerio = require('cheerio');
var by = {
	binding: function(value) {
		return attrSelector('bind', value);
	},
	model: function(value) {
		return attrSelector('model', value);
	},
	repeater: function(value) {
		return attrSelector('repeat', value);
	},
	css: function(value) {
		return value;
	},
	id: function(value) {
		return prefixSelector('#', value);
	},
	className: function(value) {
		return prefixSelector('.', value);
	},
	name: function(value) {
		return attrSelector('name', value);
	},
	tagName: function(value) {
		return attrSelector('tagName', value);
	}
};

function FindViewMatches(selectors, content) {
	loopSelectors(selectors, content);
}

function loopSelectors(selectors, content) {
	var $ = cheerio.load(content);

	selectors.forEach(function(selector) {
		var selectorString = '';
		var value = selector.value;
		var type = selector.type;

		if (!by[type] && type !== 'binding') {
			selector.disabled = 1;
			return;
		} else {
			selectorString = by[type](value);
		}

		if ($(selectorString).length) {
			selector.found = 1;
		}

		var byBinding = by.binding(value);
		if (type === 'binding' &&
			($(byBinding).length || bindExists(value, content))) {
			selector.found = 1;
		}
	});
}

function attrSelector(attr, value) {
	// 'ng:' excluded, since it's not a `cheerio` valid selector.
	var ngPrefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-'];
	var output = [];

	for (var i = 0; i < ngPrefixes.length; i = i + 1) {
		output.push(
			'[' + ngPrefixes[i] + attr + '="' + value + '"]' +
			(i === (ngPrefixes.length - 1) ? '' : ', ')
		);
	}

	return output.join('');
}

function prefixSelector(prefix, value) {
	return value.indexOf(prefix) === 0 ? value : prefix + value;
}

function bindExists(bind, contents) {
	var results;
	var exists = 0;
	var pattern = /{{(.*?)}}/gi;

	while ((results = pattern.exec(contents)) !== null) {
		if (results[1].indexOf(bind) >= 0) {
			exists = 1;
		}
	}

	return exists;
}

module.exports = FindViewMatches;
<|endoftext|>