MooTools 1.3

Fuel.shell=new Class({Extends:Fuel,language:"shell",initialize:function(a,b){this.keywords=new Hash({keywords:{csv:"if, fi, then, elif, else, for, do, done, until, while, break, continue, case, function, return, in, eq, ne, gt, lt, ge, le",alias:"kw0"},commands:{csv:"alias, apropos, awk, bash, bc, bg, builtin, bzip2, cal, cat, cd, cfdisk, chgrp, chmod, chown, chrootcksum, clear, cmp, comm, command, cp, cron, crontab, csplit, cut, date, dc, dd, ddrescue, declare, df, diff, diff3, dig, dir, dircolors, dirname, dirs, du, echo, egrep, eject, enable, env, ethtool, eval, exec, exit, expand, export, expr, false, fdformat, fdisk, fg, fgrep, file, find, fmt, fold, format, free, fsck, ftp, gawk, getopts, grep, groups, gzip, hash, head, history, hostname, id, ifconfig, import, install, join, kill, less, let, ln, local, locate, logname, logout, look, lpc, lpr, lprint, lprintd, lprintq, lprm, ls, lsof, make, man, mkdir, mkfifo, mkisofs, mknod, more, mount, mtools, mv, netstat, nice, nl, nohup, nslookup, open, op, passwd, paste, pathchk, ping, popd, pr, printcap, printenv, printf, ps, pushd, pwd, quota, quotacheck, quotactl, ram, rcp, read, readonly, renice, remsync, rm, rmdir, rsync, screen, scp, sdiff, sed, select, seq, set, sftp, shift, shopt, shutdown, sleep, sort, source, split, ssh, strace, su, sudo, sum, symlink, sync, tail, tar, tee, test, time, times, touch, top, traceroute, trap, tr, true, tsort, tty, type, ulimit, umask, umount, unalias, uname, unexpand, uniq, units, unset, unshar, useradd, usermod, users, uuencode, uudecode, v, vdir, vi, watch, wc, whereis, which, who, whoami, wget, xargs, yes",alias:"kw1"}}),this.patterns=new Hash({poundComments:{pattern:this.common.poundComments,alias:"co0"},strings:{pattern:this.common.strings,alias:"st0"}});this.parent(a,b)}});

/*
---
description: Code parsing engine for Lighter.

license: MIT-style

authors:
- Jose Prado

requires:
  core/1.2.4: '*'

provides: [Fuel]

...
*/
var Fuel = new Class({

	Implements: [Options],
	options: {
		matchType: "standard",
		strict: false
	},
	language: '',

	patterns: new Hash(),
	keywords: new Hash(),
	delimiters: new Hash({
		start: null,
		end: null
	}),

	/************************
	 * Common Regex Rules
	 ***********************/
	common: {
		slashComments: /(?:^|[^\\])\/\/.*$/gm, // Matches a C style single-line comment.
		poundComments: /#.*$/gm,               // Matches a Perl style single-line comment.
		multiComments: /\/\*[\s\S]*?\*\//gm,   // Matches a C style multi-line comment.
		aposStrings:   /'[^'\\]*(?:\\.[^'\\]*)*'/gm, // Matches a string enclosed by single quotes. Legacy.
		quotedStrings: /"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches a string enclosed by double quotes. Legacy.
		multiLineSingleQuotedStrings: /'[^'\\]*(?:\\.[^'\\]*)*'/gm, // Matches a string enclosed by single quotes across multiple lines.
		multiLineDoubleQuotedStrings: /"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches a string enclosed by double quotes across multiple lines.
		multiLineStrings:   /'[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches both.
		singleQuotedString: /'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'/gm, // Matches a string enclosed by single quotes.
		doubleQuotedString: /"[^"\\\r\n]*(?:\\.[^"\\\r\n]*)*"/gm, // Matches a string enclosed by double quotes.
		strings: /'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'|"[^"\\\r\n]*(?:\\.[^"\\\r\n]*)*"/gm, // Matches both.
		properties:    /\.([\w]+)\s*/gi,     // Matches a property: .property style.
		methodCalls:   /\.([\w]+)\s*\(/gm,   // Matches a method call: .methodName() style.
		functionCalls: /\b([\w]+)\s*\(/gm,   // Matches a function call: functionName() style.
		brackets:      /\{|\}|\(|\)|\[|\]/g, // Matches any of the common brackets.
		numbers:       /\b((?:(\d+)?\.)?[0-9]+|0x[0-9A-F]+)\b/gi // Matches integers, decimals, hexadecimals.
	},

	/************************
	 * Fuel Constructor
	 ***********************/
	initialize: function(code, options, wicks) {
		this.setOptions(options);
		this.wicks = wicks || [];
		this.code = code;
		this.aliases = $H();
		this.rules = $H();

		// Set builder object for matchType.
		this.builder = new Hash({
			'standard': this.findMatches,
			'lazy':     this.findMatchesLazy
		});

		// Add delimiter rules if not in strict mode
		if (!options.strict) {
			if (this.delimiters.start) this.addFuel('delimBeg', this.delimiters.start, 'de1');
			if (this.delimiters.end)   this.addFuel('delimEnd', this.delimiters.end,   'de2');
		}

		// Set Keyword Rules from this.keywords object.
		this.keywords.each(function(keywordSet, ruleName) {
			if (keywordSet.csv != '') {
				this.addFuel(ruleName, this.csvToRegExp(keywordSet.csv, keywordSet.mod || "g"), keywordSet.alias);
			}
		}, this);

		// Set Rules from this.patterns object.
		this.patterns.each(function(regex, ruleName) {
			this.addFuel(ruleName, regex.pattern, regex.alias);
		}, this);

		/** Process source code based on match type. */
		var codeBeg = 0,
		    codeEnd = this.code.length,
		    codeSeg = '',
		    delim   = this.delimiters,
		    matches = [],
		    match   = null,
		    endMatch = null;

		if (!options.strict) {
			// Find matches through the complete source code.
			matches.extend(this.builder[options.matchType].pass(this.code, this)());
		} else if (delim.start && delim.end) {
			// Find areas between language delimiters and find matches there.
			while ((match = delim.start.exec(this.code)) != null ) {
				delim.end.lastIndex = delim.start.lastIndex;
				if ((endMatch = delim.end.exec(this.code)) != null ) {
					matches.push(new Wick(match[0], 'de1', match.index));
					codeBeg = delim.start.lastIndex;
					codeEnd = endMatch.index-1;
					codeSeg = this.code.substring(codeBeg, codeEnd);
					matches.extend(this.builder[options.matchType].pass([codeSeg, codeBeg], this)());
					matches.push(new Wick(endMatch[0], 'de2', endMatch.index));
				}
			}
		}
		this.wicks = matches;
	},

	/************************
	 * Regex Helper methods.
	 ***********************/
	addFuel: function(fuelName, RegEx, className) {
		this.rules[fuelName] = RegEx;
		this.addAlias(fuelName, className);
	},
	addAlias: function(key, alias) { this.aliases[key] = alias || key; },
	csvToRegExp: function(csv, mod) {return new RegExp('\\b(' + csv.replace(/,\s*/g, '|') + ')\\b', mod);},
	delimToRegExp: function(beg, esc, end, mod, suffix) {
		beg = beg.escapeRegExp();
		if (esc) esc = esc.escapeRegExp();
		end = (end) ? end.escapeRegExp() : beg;
		var pat = (esc) ? beg+"[^"+end+esc+'\\n]*(?:'+esc+'.[^'+end+esc+'\\n]*)*'+end : beg+"[^"+end+'\\n]*'+end;
		return new RegExp(pat+(suffix || ''), mod || '');
	},
	strictRegExp: function() {
		var regex = '(';
		for (var i = 0; i < arguments.length; i++) {
			regex += arguments[i].escapeRegExp();
			regex += (i < arguments.length - 1) ? '|' : '';
		}
		regex += ')';
		return new RegExp(regex, "gim");
	},

	/************************
	 * Match finding Methods
	 ***********************/
	findMatches: function(code, offset) {
		var wicks       = [],
		    startIndex  = 0,
		    matchIndex  = code.length
		    insertIndex = 0,
		    match      = null,
		    type       = null,
		    newWick    = null,
		    rule       = null,
		    rules      = {},
		    currentMatch = null,
		    futureMatch  = null;

		offset = offset || 0;

		// Create assosciative array of rules for faster access via for...in loop instead of .each().
		this.rules.each(function(regex, rule) {
			rules[rule] = {pattern: regex, nextIndex: 0};
		}, this);

		/**
		 * Step through the source code sequentially finding the left-most/earliest matches and then
		 * continuing beyond the end of that match to prevent parser from adding inner matches.
		 */
		while(startIndex < code.length) {
			matchIndex = code.length;
			match      = null;

			// Apply each rule at the current startIndex.
			for (rule in rules) {
				rules[rule].pattern.lastIndex = startIndex;
				currentMatch = rules[rule].pattern.exec(code);
				if (currentMatch === null) {
					// Delete rule if there's no matches.
					delete rules[rule];
				} else {
					// Find earliest and longest match, then store relevant info.
					if (currentMatch.index < matchIndex || (currentMatch.index == matchIndex && match[0].length < currentMatch[0].length)) {
						match      = currentMatch;
						type       = rule;
						matchIndex = currentMatch.index;
					}
					// Store index of rules' next match in nextIndex property.
					rules[rule].nextIndex = rules[rule].pattern.lastIndex - currentMatch[0].length;
				}
			}
			/* Create a new Wick out of found match. Otherwise break out of loop since no
			   matches are left. */
			if (match != null) {

				// If $1 capture group exists, use $1 instead of full match.
				index = (match[1] && match[0].contains(match[1])) ? match.index + match[0].indexOf(match[1]) : match.index;
				newWick = new Wick(match[1] || match[0], type, index+offset);
				wicks.push(newWick);

				/* Find the next match of current rule and store its index. If not done, the nextIndex
				   would be at the start of current match, thus creating an infinite loop*/
				futureMatch = rules[type].pattern.exec(code);
				if (!futureMatch) {
					rules[type].nextIndex = code.length;
				} else {
					rules[type].nextIndex = rules[type].pattern.lastIndex - futureMatch[0].length;
				}

				// Cycle through "nextIndex" properties and store earliest position in min variable.
				var min = code.length;
				for (rule in rules) {
					if (rules[rule].nextIndex < min) {
						min = rules[rule].nextIndex;
					}
				}
				/* Set startIndex to the end of current match if min is located behind it. Normally this
				   would signal an inner match. Future upgrades should do this test in the min loop
				   in order to find the actual earliest match. */
				startIndex = Math.max(min, newWick.end - offset);
			} else {
				break;
			}
		}
		return wicks;
	},
	/* Brute force the matches by finding all possible matches from all rules. Then we sort them
	   and cycle through the matches finding and eliminating inner matches. Faster than findMatches,
	   but less robust and prone to erroneous matches. */
	findMatchesLazy: function(code, offset) {
		var wicks = this.wicks,
		    match = null
		    index = 0;

		offset = offset || 0;

		this.rules.each(function(regex, rule) {
			while ((match = regex.exec(code)) != null) {
				index = (match[1] && match[0].contains(match[1])) ? match.index + match[0].indexOf(match[1]) : match.index;
				wicks.push(new Wick(match[1] || match[0], rule, index + offset));
			}
		}, this);
		return this.purgeWicks(wicks);
	},
	purgeWicks: function(wicks) {
		wicks = wicks.sort(this.compareWicks);
		for (var i = 0, j = 0; i < wicks.length; i++) {
			if (wicks[i] == null) continue;
			for (j = i+1; j < wicks.length && wicks[i] != null; j++) {
				if      (wicks[j] == null)            {continue;}
				else if (wicks[j].isBeyond(wicks[i])) {break;}
				else if (wicks[j].overlaps(wicks[i])) {wicks[i] = null;}
				else if (wicks[i].contains(wicks[j])) {wicks[j] = null;}
			}
		}
		return wicks.clean();
	},
	compareWicks: function(wick1, wick2) {return wick1.index - wick2.index;}
});

Fuel.standard = new Class({ Extends: Fuel, initialize: function(code, options, wicks) { this.parent(code, options, wicks); } });

var Wick = new Class({

	initialize: function(match, type, index) {
		this.text   = match;
		this.type   = type;
		this.index  = index;
		this.length = this.text.length;
		this.end    = this.index + this.length;
	},
	contains: function(wick) { return (wick.index >= this.index && wick.index < this.end); },
	isBeyond: function(wick) { return (this.index >= wick.end); },
	overlaps: function(wick) { return (this.index == wick.index && this.length > wick.length); },
	toString: function() { return this.index+' - '+this.text+' - '+this.end; }
});

/*
---
description: Builds and displays an element containing highlighted code bits.

license: MIT-style

authors:
- Jose Prado

requires:
  core/1.2.4: '*'

provides: [Lighter]

...
*/
(function() {
	Lighter = new Class({

		Implements: [Options],
		name: 'Lighter',

		options: {
			altLines: '', // Pseudo-selector enabled.
			clipboard: null,
			container: null,
			editable: false,
			flame: 'standard',
			fuel:  'standard',
			id: null,
			indent: -1,
			matchType: "standard",
			mode: "pre",
			path: null,
			strict: false
		},

		initialize: function(codeblock, options) {
			this.setOptions(options);
			this.id = this.options.id || this.name + '_' + $time();
			this.codeblock = $(codeblock);
			this.container = $(this.options.container);
			this.code = chop(this.codeblock.get('html')).replace(/</gim, '<').replace(/>/gim, '>').replace(/&/gim, '&');

			// Indent code if user option is set.
			if (this.options.indent > -1) { this.code = tabToSpaces(this.code, this.options.indent); }

			// Figure out path based on script location of Lighter.js or option passed in.
			this.getPath();

			// Extract fuel/flame names. Precedence: className > options > 'standard'.
			this.getClass();

			// Set builder options.
			this.builder = new Hash({
				'inline': this.createLighter.pass('code', this),
				'pre':    this.createLighter.pass('pre', this),
				'ol':     this.createLighterWithLines.pass([['ol'], ['li']], this),
				'div':    this.createLighterWithLines.pass([['div'], ['div', 'span'], true, 'span'], this),
				'table':  this.createLighterWithLines.pass([['table', 'tbody'], ['tr', 'td'], true, 'td'], this)
			});

			// Initialize caches
			Lighter.scripts     = Lighter.scripts || {};
			Lighter.stylesheets = Lighter.stylesheets || {};

			// Load fuel/flame to start chain of loads.
			this.loadStylesheet(this.options.flame, 'Flame.'+this.options.flame+'.css');
			this.loadFuel();
		},

		loadFuel: function() {
			try {
				this.fuel = new Fuel[this.options.fuel](this.code, {
					matchType: this.options.matchType,
					strict: this.options.strict
				});
				this.light();
			} catch (e) {
				this.loadScript(this.options.fuel, 'Fuel.'+this.options.fuel+'.js', {
					'load': this.loadFuel.bind(this),
					'error': function() { this.options.fuel = 'standard'; this.loadFuel(); }.bind(this)
				});
			}
		},

		light: function() {
			// Build highlighted code object.
			this.element = this.toElement();

			// Insert lighter in the right spot.
			if (this.container) {
				this.container.empty();
				this.element.inject(this.container);
			} else {
				this.codeblock.setStyle('display', 'none');
				this.element.inject(this.codeblock, 'after');
				if (this.options.clipboard) { this.loadClipboard(); }
			}
		},

		unlight: function() {
			$(this).setStyle('display', 'none');
			this.codeblock.setStyle('display', 'inherit');
		},

		loadClipboard: function() {
			try {
				var clip = new ZeroClipboard.Client();
				clip.setPath(this.options.path);
				clip.glue($(this.options.clipboard));
				clip.setText(this.code);
				clip.addEventListener('complete', function(client, text) {
		        alert("Copied text to clipboard:\n" + text);
		    });
			} catch (e) {
				this.loadScript('clipboard', 'ZeroClipboard.js', {
					'load': this.loadClipboard.bind(this),
					'error': $empty
				});
				return false;
			}
		},

		/* ----------------------------------- */
		/* ---->>> INIT HELPER METHODS <<<---- */
		/* ----------------------------------- */
		getPath: function() {
		  if (!$chk(Lighter.path)) {
		      $$('head script').each(function(el) {
		          var script  = el.src.split('?', 1),
		              pattern = /Lighter(\.full|\.lite)?\.js$/gi;
		          if (script[0].match(pattern)) {
		              Lighter.path = script[0].replace(pattern, '');
		          }
		      })
		  }
		  if (!this.options.path) { this.options.path = Lighter.path; }
		},

		getClass: function() {
            var classNames = this.codeblock.get('class').split(' '),
                ff = [null, null];
            switch (classNames.length) {
                case 0: // No language! Simply wrap in Lighter.js standard Fuel/Flame.
					break;
				case 1: // Single class, assume this is the fuel/flame
					ff = classNames[0].split(':');
					break;
				default: // More than one class, let's give the first one priority for now.
					ff = classNames[0].split(':');
            }

            if (ff[0]) { this.options.fuel  = ff[0]; }
            if (ff[1]) { this.options.flame = ff[1]; }
		},

		loadScript: function(holder, fileName, events) {
			if ($chk(Lighter.scripts[holder])) {
				Lighter.scripts[holder].addEvents({
					load: events.load,
					error: events.error,
					readystatechange: function() {
						if (['loaded', 'complete'].contains(this.readyState)) events.load();
					}
				});
			} else {
				Lighter.scripts[holder] = new Element('script', {
					'src': this.options.path+fileName+'?'+$time(),
					'type': 'text/javascript',
					'events': {
						load: events.load,
						error: events.error,
						readystatechange: function() {
							if (['loaded', 'complete'].contains(this.readyState)) events.load();
						}
					}
				}).inject(document.head);
			}
		},

		loadStylesheet: function(holder, fileName) {
			if (!$chk(Lighter.stylesheets[holder])) {
				Lighter.stylesheets[holder] = new Element('link', {
					rel: "stylesheet",
					type: "text/css",
					media: "screen",
					href: this.options.path+fileName+'?'+$time()
				}).inject(document.head);
			}
		},

		/* ----------------------------------- */
		/* -->>> ELEMENT CREATE METHODS <<<--- */
		/* ----------------------------------- */
		createLighter: function(parent) {
			var lighter = new Element(parent, { 'class': this.options.flame + this.name }),
			    pointer = 0;

			// If no matches were found, insert code plain text.
			if (!$defined(this.fuel.wicks[0])) {
				lighter.appendText(this.code);
			} else {

				// Step through each match and add unmatched + matched bits to lighter.
				this.fuel.wicks.each(function(match) {
					lighter.appendText(this.code.substring(pointer, match.index));

					this.insertAndKeepEl(lighter, match.text, match.type);
					pointer = match.index + match.text.length;
				}, this);

				// Add last unmatched code segment if it exists.
				if (pointer < this.code.length) {
					lighter.appendText(this.code.substring(pointer, this.code.length));
				}
			}

			//lighter.set('text', lighter.get('html'));
			return lighter;
		},

		createLighterWithLines: function(parent, child, addLines, numType) {
			var lighter = new Element(parent[0], { 'class': this.options.flame + this.name, 'id': this.id }),
			    newLine = new Element(child[0]),
			    lineNum = 1,
			    pointer = 0,
			    text = null;

			// Small hack to ensure tables have no ugly styles.
			if (parent[0] == "table") { lighter.set("cellpadding", 0).set("cellspacing", 0).set("border", 0); }

			/* If lines need to be wrapped in an inner parent, create that element
			   with this test. (E.g, tbody in a table) */
			if (parent[1]) { lighter = new Element(parent[1]).inject(lighter); }

			/* If code needs to be wrapped in an inner child, create that element
			   with this test. (E.g, tr to contain td) */
			if (child[1]) { newLine = new Element(child[1]).inject(newLine); }
			newLine.addClass(this.options.flame + 'line');
			if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }

			// Step through each match and add matched/unmatched bits to lighter.
			this.fuel.wicks.each(function(match) {

				// Create and insert un-matched source code bits.
				if (pointer != match.index) {
					text = this.code.substring(pointer, match.index).split("\n");
					for (var i = 0; i < text.length; i++) {
						if (i < text.length - 1) {
							if (text[i] === '') { text[i] = ' '; }
							newLine = this.insertAndMakeEl(newLine, lighter, text[i], child);
							if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }
						} else {
							this.insertAndKeepEl(newLine, text[i]);
						}
					}
				}

				// Create and insert matched symbol.
				text = match.text.split('\n');
				for (var i = 0; i < text.length; i++) {
					if (i < text.length - 1) {
						newLine = this.insertAndMakeEl(newLine, lighter, text[i], child, match.type);
						if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }
					} else {
						this.insertAndKeepEl(newLine, text[i], match.type);
					}
				}

				pointer = match.end;
			}, this);

			// Add last unmatched code segment if it exists.
			if (pointer <= this.code.length) {
				text = this.code.substring(pointer, this.code.length).split('\n');
				for (var i = 0; i < text.length; i++) {
					newLine = this.insertAndMakeEl(newLine, lighter, text[i], child);
					if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }
				}
			}

			// Add alternate line styles based on pseudo-selector.
			if (this.options.altLines !== '') {
				if (this.options.altLines == 'hover') {
					lighter.getElements('.'+this.options.flame+'line').addEvents({
							'mouseover': function() {this.toggleClass('alt');},
							'mouseout':  function() {this.toggleClass('alt');}
					});
				} else {
					if (child[1]) {
						lighter.getChildren(':'+this.options.altLines).getElement('.'+this.options.flame+'line').addClass('alt');
					} else {
						lighter.getChildren(':'+this.options.altLines).addClass('alt');
					}
				}
			}

			// Add first/last line classes to correct element based on mode.
			if (child[1]) {
				lighter.getFirst().getChildren().addClass(this.options.flame+'first');
				lighter.getLast().getChildren().addClass(this.options.flame+'last');
			} else {
				lighter.getFirst().addClass(this.options.flame+'first');
				lighter.getLast().addClass(this.options.flame+'last');
			}

			// Ensure we return the real parent, not just an inner element like a tbody.
			if (parent[1]) { lighter = lighter.getParent(); }
			return lighter;
		},

		/** Helper function to insert new code segment into existing line. */
		insertAndKeepEl: function(el, text, alias) {
			if (text.length > 0) {
				var span = new Element('span', { 'text': text });
				if (alias) {
					span.addClass(this.fuel.aliases[alias] || alias);
				}
				span.inject(el);
			}
		},

		/** Helper function to insert new code segment into existing line and create new line. */
		insertAndMakeEl: function(el, group, text, child, alias) {
			this.insertAndKeepEl(el, text, alias);
			if (child[1]) { el = el.getParent(); }
			el.inject(group);

			var newLine = new Element(child[0]);
			if (child[1]) { newLine = new Element(child[1]).inject(newLine); }
			newLine.addClass(this.options.flame+'line');
			return newLine;
		},

		/** Helper funciton to insert line number into line. */
		insertLineNum: function(el, lineNum, elType) {
			var newNum = new Element(elType, {
				'text':  lineNum++,
				'class': this.options.flame+ 'num'
			});
			newNum.inject(el.getParent(), 'top');

			return lineNum;
		},

		/* ----------------------------------- */
		/* ------>>> toType METHODS <<<------- */
		/* ----------------------------------- */
		toElement: function() {
			if (!this.element) {
				this.element = this.builder[this.options.mode]();
				if (this.options.editable) { this.element.set('contenteditable', 'true'); }
			}

			return this.element;
		},

		toString: function() {
			return this.code;
		}

	});

	/** Element Native extensions */
	Element.implement({ light: function(options){ return new Lighter(this, options); } });

	/** String functions */
	function chop(str) { return str.replace(/(^\s*\n|\n\s*$)/gi, ''); }
	function tabToSpaces(str, spaces) {
		for (var i = 0, indent = ''; i < spaces; i++) { indent += ' '; }
		return str.replace(/\t/g, indent);
	}

})();

Fuel.shell=new Class({Extends:Fuel,language:"shell",initialize:function(a,b){this.keywords=new Hash({keywords:{csv:"if, fi, then, elif, else, for, do, done, until, while, break, continue, case, function, return, in, eq, ne, gt, lt, ge, le",alias:"kw0"},commands:{csv:"alias, apropos, awk, bash, bc, bg, builtin, bzip2, cal, cat, cd, cfdisk, chgrp, chmod, chown, chrootcksum, clear, cmp, comm, command, cp, cron, crontab, csplit, cut, date, dc, dd, ddrescue, declare, df, diff, diff3, dig, dir, dircolors, dirname, dirs, du, echo, egrep, eject, enable, env, ethtool, eval, exec, exit, expand, export, expr, false, fdformat, fdisk, fg, fgrep, file, find, fmt, fold, format, free, fsck, ftp, gawk, getopts, grep, groups, gzip, hash, head, history, hostname, id, ifconfig, import, install, join, kill, less, let, ln, local, locate, logname, logout, look, lpc, lpr, lprint, lprintd, lprintq, lprm, ls, lsof, make, man, mkdir, mkfifo, mkisofs, mknod, more, mount, mtools, mv, netstat, nice, nl, nohup, nslookup, open, op, passwd, paste, pathchk, ping, popd, pr, printcap, printenv, printf, ps, pushd, pwd, quota, quotacheck, quotactl, ram, rcp, read, readonly, renice, remsync, rm, rmdir, rsync, screen, scp, sdiff, sed, select, seq, set, sftp, shift, shopt, shutdown, sleep, sort, source, split, ssh, strace, su, sudo, sum, symlink, sync, tail, tar, tee, test, time, times, touch, top, traceroute, trap, tr, true, tsort, tty, type, ulimit, umask, umount, unalias, uname, unexpand, uniq, units, unset, unshar, useradd, usermod, users, uuencode, uudecode, v, vdir, vi, watch, wc, whereis, which, who, whoami, wget, xargs, yes",alias:"kw1"}}),this.patterns=new Hash({poundComments:{pattern:this.common.poundComments,alias:"co1"},strings:{pattern:this.common.strings,alias:"st0"}});this.parent(a,b)}});

/*
---
description: Code parsing engine for Lighter.

license: MIT-style

authors:
- Jose Prado

requires:
  core/1.2.4: '*'

provides: [Fuel]

...
*/
var Fuel = new Class({

	Implements: [Options],
	options: {
		matchType: "standard",
		strict: false
	},
	language: '',

	patterns: new Hash(),
	keywords: new Hash(),
	delimiters: new Hash({
		start: null,
		end: null
	}),

	/************************
	 * Common Regex Rules
	 ***********************/
	common: {
		slashComments: /(?:^|[^\\])\/\/.*$/gm, // Matches a C style single-line comment.
		poundComments: /#.*$/gm,               // Matches a Perl style single-line comment.
		multiComments: /\/\*[\s\S]*?\*\//gm,   // Matches a C style multi-line comment.
		aposStrings:   /'[^'\\]*(?:\\.[^'\\]*)*'/gm, // Matches a string enclosed by single quotes. Legacy.
		quotedStrings: /"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches a string enclosed by double quotes. Legacy.
		multiLineSingleQuotedStrings: /'[^'\\]*(?:\\.[^'\\]*)*'/gm, // Matches a string enclosed by single quotes across multiple lines.
		multiLineDoubleQuotedStrings: /"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches a string enclosed by double quotes across multiple lines.
		multiLineStrings:   /'[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"/gm, // Matches both.
		singleQuotedString: /'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'/gm, // Matches a string enclosed by single quotes.
		doubleQuotedString: /"[^"\\\r\n]*(?:\\.[^"\\\r\n]*)*"/gm, // Matches a string enclosed by double quotes.
		strings: /'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'|"[^"\\\r\n]*(?:\\.[^"\\\r\n]*)*"/gm, // Matches both.
		properties:    /\.([\w]+)\s*/gi,     // Matches a property: .property style.
		methodCalls:   /\.([\w]+)\s*\(/gm,   // Matches a method call: .methodName() style.
		functionCalls: /\b([\w]+)\s*\(/gm,   // Matches a function call: functionName() style.
		brackets:      /\{|\}|\(|\)|\[|\]/g, // Matches any of the common brackets.
		numbers:       /\b((?:(\d+)?\.)?[0-9]+|0x[0-9A-F]+)\b/gi // Matches integers, decimals, hexadecimals.
	},

	/************************
	 * Fuel Constructor
	 ***********************/
	initialize: function(code, options, wicks) {
		this.setOptions(options);
		this.wicks = wicks || [];
		this.code = code;
		this.aliases = $H();
		this.rules = $H();

		// Set builder object for matchType.
		this.builder = new Hash({
			'standard': this.findMatches,
			'lazy':     this.findMatchesLazy
		});

		// Add delimiter rules if not in strict mode
		if (!options.strict) {
			if (this.delimiters.start) this.addFuel('delimBeg', this.delimiters.start, 'de1');
			if (this.delimiters.end)   this.addFuel('delimEnd', this.delimiters.end,   'de2');
		}

		// Set Keyword Rules from this.keywords object.
		this.keywords.each(function(keywordSet, ruleName) {
			if (keywordSet.csv != '') {
				this.addFuel(ruleName, this.csvToRegExp(keywordSet.csv, keywordSet.mod || "g"), keywordSet.alias);
			}
		}, this);

		// Set Rules from this.patterns object.
		this.patterns.each(function(regex, ruleName) {
			this.addFuel(ruleName, regex.pattern, regex.alias);
		}, this);

		/** Process source code based on match type. */
		var codeBeg = 0,
		    codeEnd = this.code.length,
		    codeSeg = '',
		    delim   = this.delimiters,
		    matches = [],
		    match   = null,
		    endMatch = null;

		if (!options.strict) {
			// Find matches through the complete source code.
            Object.append(matches, this.builder[options.matchType].pass(this.code, this)());
		} else if (delim.start && delim.end) {
			// Find areas between language delimiters and find matches there.
			while ((match = delim.start.exec(this.code)) != null ) {
				delim.end.lastIndex = delim.start.lastIndex;
				if ((endMatch = delim.end.exec(this.code)) != null ) {
					matches.push(new Wick(match[0], 'de1', match.index));
					codeBeg = delim.start.lastIndex;
					codeEnd = endMatch.index-1;
					codeSeg = this.code.substring(codeBeg, codeEnd);
					matches.extend(this.builder[options.matchType].pass([codeSeg, codeBeg], this)());
					matches.push(new Wick(endMatch[0], 'de2', endMatch.index));
				}
			}
		}
		this.wicks = matches;
	},

	/************************
	 * Regex Helper methods.
	 ***********************/
	addFuel: function(fuelName, RegEx, className) {
		this.rules[fuelName] = RegEx;
		this.addAlias(fuelName, className);
	},
	addAlias: function(key, alias) { this.aliases[key] = alias || key; },
	csvToRegExp: function(csv, mod) {return new RegExp('\\b(' + csv.replace(/,\s*/g, '|') + ')\\b', mod);},
	delimToRegExp: function(beg, esc, end, mod, suffix) {
		beg = beg.escapeRegExp();
		if (esc) esc = esc.escapeRegExp();
		end = (end) ? end.escapeRegExp() : beg;
		var pat = (esc) ? beg+"[^"+end+esc+'\\n]*(?:'+esc+'.[^'+end+esc+'\\n]*)*'+end : beg+"[^"+end+'\\n]*'+end;
		return new RegExp(pat+(suffix || ''), mod || '');
	},
	strictRegExp: function() {
		var regex = '(';
		for (var i = 0; i < arguments.length; i++) {
			regex += arguments[i].escapeRegExp();
			regex += (i < arguments.length - 1) ? '|' : '';
		}
		regex += ')';
		return new RegExp(regex, "gim");
	},

	/************************
	 * Match finding Methods
	 ***********************/
	findMatches: function(code, offset) {
		var wicks       = [],
		    startIndex  = 0,
		    matchIndex  = code.length
		    insertIndex = 0,
		    match      = null,
		    type       = null,
		    newWick    = null,
		    rule       = null,
		    rules      = {},
		    currentMatch = null,
		    futureMatch  = null;

		offset = offset || 0;

		// Create assosciative array of rules for faster access via for...in loop instead of .each().
		this.rules.each(function(regex, rule) {
			rules[rule] = {pattern: regex, nextIndex: 0};
		}, this);

		/**
		 * Step through the source code sequentially finding the left-most/earliest matches and then
		 * continuing beyond the end of that match to prevent parser from adding inner matches.
		 */
		while(startIndex < code.length) {
			matchIndex = code.length;
			match      = null;

			// Apply each rule at the current startIndex.
			for (rule in rules) {
				rules[rule].pattern.lastIndex = startIndex;
				currentMatch = rules[rule].pattern.exec(code);
				if (currentMatch === null) {
					// Delete rule if there's no matches.
					delete rules[rule];
				} else {
					// Find earliest and longest match, then store relevant info.
					if (currentMatch.index < matchIndex || (currentMatch.index == matchIndex && match[0].length < currentMatch[0].length)) {
						match      = currentMatch;
						type       = rule;
						matchIndex = currentMatch.index;
					}
					// Store index of rules' next match in nextIndex property.
					rules[rule].nextIndex = rules[rule].pattern.lastIndex - currentMatch[0].length;
				}
			}
			/* Create a new Wick out of found match. Otherwise break out of loop since no
			   matches are left. */
			if (match != null) {

				// If $1 capture group exists, use $1 instead of full match.
				index = (match[1] && match[0].contains(match[1])) ? match.index + match[0].indexOf(match[1]) : match.index;
				newWick = new Wick(match[1] || match[0], type, index+offset);
				wicks.push(newWick);

				/* Find the next match of current rule and store its index. If not done, the nextIndex
				   would be at the start of current match, thus creating an infinite loop*/
				futureMatch = rules[type].pattern.exec(code);
				if (!futureMatch) {
					rules[type].nextIndex = code.length;
				} else {
					rules[type].nextIndex = rules[type].pattern.lastIndex - futureMatch[0].length;
				}

				// Cycle through "nextIndex" properties and store earliest position in min variable.
				var min = code.length;
				for (rule in rules) {
					if (rules[rule].nextIndex < min) {
						min = rules[rule].nextIndex;
					}
				}
				/* Set startIndex to the end of current match if min is located behind it. Normally this
				   would signal an inner match. Future upgrades should do this test in the min loop
				   in order to find the actual earliest match. */
				startIndex = Math.max(min, newWick.end - offset);
			} else {
				break;
			}
		}
		return wicks;
	},
	/* Brute force the matches by finding all possible matches from all rules. Then we sort them
	   and cycle through the matches finding and eliminating inner matches. Faster than findMatches,
	   but less robust and prone to erroneous matches. */
	findMatchesLazy: function(code, offset) {
		var wicks = this.wicks,
		    match = null
		    index = 0;

		offset = offset || 0;

		this.rules.each(function(regex, rule) {
			while ((match = regex.exec(code)) != null) {
				index = (match[1] && match[0].contains(match[1])) ? match.index + match[0].indexOf(match[1]) : match.index;
				wicks.push(new Wick(match[1] || match[0], rule, index + offset));
			}
		}, this);
		return this.purgeWicks(wicks);
	},
	purgeWicks: function(wicks) {
		wicks = wicks.sort(this.compareWicks);
		for (var i = 0, j = 0; i < wicks.length; i++) {
			if (wicks[i] == null) continue;
			for (j = i+1; j < wicks.length && wicks[i] != null; j++) {
				if      (wicks[j] == null)            {continue;}
				else if (wicks[j].isBeyond(wicks[i])) {break;}
				else if (wicks[j].overlaps(wicks[i])) {wicks[i] = null;}
				else if (wicks[i].contains(wicks[j])) {wicks[j] = null;}
			}
		}
		return wicks.clean();
	},
	compareWicks: function(wick1, wick2) {return wick1.index - wick2.index;}
});

Fuel.standard = new Class({ Extends: Fuel, initialize: function(code, options, wicks) { this.parent(code, options, wicks); } });

var Wick = new Class({

	initialize: function(match, type, index) {
		this.text   = match;
		this.type   = type;
		this.index  = index;
		this.length = this.text.length;
		this.end    = this.index + this.length;
	},
	contains: function(wick) { return (wick.index >= this.index && wick.index < this.end); },
	isBeyond: function(wick) { return (this.index >= wick.end); },
	overlaps: function(wick) { return (this.index == wick.index && this.length > wick.length); },
	toString: function() { return this.index+' - '+this.text+' - '+this.end; }
});

/*
---
description: Builds and displays an element containing highlighted code bits.

license: MIT-style

authors:
- Jose Prado

requires:
  core/1.3: '*'

provides: [Lighter]

...
*/
(function() {
	Lighter = new Class({

		Implements: [Options],
		name: 'Lighter',

		options: {
			altLines: '', // Pseudo-selector enabled.
			clipboard: null,
			container: null,
			editable: false,
			flame: 'standard',
			fuel:  'standard',
			id: null,
			indent: -1,
			matchType: "standard",
			mode: "pre",
			path: null,
			strict: false
		},

		initialize: function(codeblock, options) {
			this.setOptions(options);
			this.id = this.options.id || this.name + '_' + Date.now();
			this.codeblock = $(codeblock);
			this.container = $(this.options.container);
			this.code = chop(this.codeblock.get('html')).replace(/</gim, '<').replace(/>/gim, '>').replace(/&/gim, '&');

			// Indent code if user option is set.
			if (this.options.indent > -1) { this.code = tabToSpaces(this.code, this.options.indent); }

			// Figure out path based on script location of Lighter.js or option passed in.
			this.getPath();

			// Extract fuel/flame names. Precedence: className > options > 'standard'.
			this.getClass();

			// Set builder options.
			this.builder = new Hash({
				'inline': this.createLighter.pass('code', this),
				'pre':    this.createLighter.pass('pre', this),
				'ol':     this.createLighterWithLines.pass([['ol'], ['li']], this),
				'div':    this.createLighterWithLines.pass([['div'], ['div', 'span'], true, 'span'], this),
				'table':  this.createLighterWithLines.pass([['table', 'tbody'], ['tr', 'td'], true, 'td'], this)
			});

			// Initialize caches
			Lighter.scripts     = Lighter.scripts || {};
			Lighter.stylesheets = Lighter.stylesheets || {};

			// Load fuel/flame to start chain of loads.
			this.loadStylesheet(this.options.flame, 'Flame.'+this.options.flame+'.css');
			this.loadFuel();
		},

		loadFuel: function() {
			try {
				this.fuel = new Fuel[this.options.fuel](this.code, {
					matchType: this.options.matchType,
					strict: this.options.strict
				});
				this.light();
			} catch (e) {
				this.loadScript(this.options.fuel, 'Fuel.'+this.options.fuel+'.js', {
					'load': this.loadFuel.bind(this),
					'error': function() { this.options.fuel = 'standard'; this.loadFuel(); }.bind(this)
				});
			}
		},

		light: function() {
			// Build highlighted code object.
			this.element = this.toElement();

			// Insert lighter in the right spot.
			if (this.container) {
				this.container.empty();
				this.element.inject(this.container);
			} else {
				this.codeblock.setStyle('display', 'none');
				this.element.inject(this.codeblock, 'after');
				if (this.options.clipboard) { this.loadClipboard(); }
			}
		},

		unlight: function() {
			$(this).setStyle('display', 'none');
			this.codeblock.setStyle('display', 'inherit');
		},

		loadClipboard: function() {
			try {
				var clip = new ZeroClipboard.Client();
				clip.setPath(this.options.path);
				clip.glue($(this.options.clipboard));
				clip.setText(this.code);
				clip.addEventListener('complete', function(client, text) {
		        alert("Copied text to clipboard:\n" + text);
		    });
			} catch (e) {
				this.loadScript('clipboard', 'ZeroClipboard.js', {
					'load': this.loadClipboard.bind(this),
					'error': Function.from()
				});
				return false;
			}
		},

		/* ----------------------------------- */
		/* ---->>> INIT HELPER METHODS <<<---- */
		/* ----------------------------------- */
		getPath: function() {
		  if (!Lighter.path) {
		      $$('head script').each(function(el) {
		          var script  = el.src.split('?', 1),
		              pattern = /Lighter(\.full|\.lite)?\.js$/gi;
		          if (script[0].match(pattern)) {
		              Lighter.path = script[0].replace(pattern, '');
		          }
		      })
		  }
		  if (!this.options.path) { this.options.path = Lighter.path; }
		},

		getClass: function() {
            var classNames = this.codeblock.get('class').split(' '),
                ff = [null, null];
            switch (classNames.length) {
                case 0: // No language! Simply wrap in Lighter.js standard Fuel/Flame.
					break;
				case 1: // Single class, assume this is the fuel/flame
					ff = classNames[0].split(':');
					break;
				default: // More than one class, let's give the first one priority for now.
					ff = classNames[0].split(':');
            }

            if (ff[0]) { this.options.fuel  = ff[0]; }
            if (ff[1]) { this.options.flame = ff[1]; }
		},

		loadScript: function(holder, fileName, events) {
			if (Lighter.scripts[holder] != undefined) {
				Lighter.scripts[holder].addEvents({
					load: events.load,
					error: events.error,
					readystatechange: function() {
						if (['loaded', 'complete'].contains(this.readyState)) events.load();
					}
				});
			} else {
				Lighter.scripts[holder] = new Element('script', {
					'src': this.options.path+fileName+'?'+Date.now(),
					'type': 'text/javascript',
					'events': {
						load: events.load,
						error: events.error,
						readystatechange: function() {
							if (['loaded', 'complete'].contains(this.readyState)) events.load();
						}
					}
				}).inject(document.head);
			}
		},

		loadStylesheet: function(holder, fileName) {
			if (!Lighter.stylesheets[holder]) {
				Lighter.stylesheets[holder] = new Element('link', {
					rel: "stylesheet",
					type: "text/css",
					media: "screen",
					href: this.options.path+fileName+'?'+Date.now()
				}).inject(document.head);
			}
		},

		/* ----------------------------------- */
		/* -->>> ELEMENT CREATE METHODS <<<--- */
		/* ----------------------------------- */
		createLighter: function(parent) {
			var lighter = new Element(parent, { 'class': this.options.flame + this.name }),
			    pointer = 0;

			// If no matches were found, insert code plain text.
			if (this.fuel.wicks[0] == undefined) {
				lighter.appendText(this.code);
			} else {

				// Step through each match and add unmatched + matched bits to lighter.
				this.fuel.wicks.each(function(match) {
					lighter.appendText(this.code.substring(pointer, match.index));

					this.insertAndKeepEl(lighter, match.text, match.type);
					pointer = match.index + match.text.length;
				}, this);

				// Add last unmatched code segment if it exists.
				if (pointer < this.code.length) {
					lighter.appendText(this.code.substring(pointer, this.code.length));
				}
			}

			//lighter.set('text', lighter.get('html'));
			return lighter;
		},

		createLighterWithLines: function(parent, child, addLines, numType) {
			var lighter = new Element(parent[0], { 'class': this.options.flame + this.name, 'id': this.id }),
			    newLine = new Element(child[0]),
			    lineNum = 1,
			    pointer = 0,
			    text = null;

			// Small hack to ensure tables have no ugly styles.
			if (parent[0] == "table") { lighter.set("cellpadding", 0).set("cellspacing", 0).set("border", 0); }

			/* If lines need to be wrapped in an inner parent, create that element
			   with this test. (E.g, tbody in a table) */
			if (parent[1]) { lighter = new Element(parent[1]).inject(lighter); }

			/* If code needs to be wrapped in an inner child, create that element
			   with this test. (E.g, tr to contain td) */
			if (child[1]) { newLine = new Element(child[1]).inject(newLine); }
			newLine.addClass(this.options.flame + 'line');
			if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }

			// Step through each match and add matched/unmatched bits to lighter.
			this.fuel.wicks.each(function(match) {

				// Create and insert un-matched source code bits.
				if (pointer != match.index) {
					text = this.code.substring(pointer, match.index).split("\n");
					for (var i = 0; i < text.length; i++) {
						if (i < text.length - 1) {
							if (text[i] === '') { text[i] = ' '; }
							newLine = this.insertAndMakeEl(newLine, lighter, text[i], child);
							if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }
						} else {
							this.insertAndKeepEl(newLine, text[i]);
						}
					}
				}

				// Create and insert matched symbol.
				text = match.text.split('\n');
				for (var i = 0; i < text.length; i++) {
					if (i < text.length - 1) {
						newLine = this.insertAndMakeEl(newLine, lighter, text[i], child, match.type);
						if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }
					} else {
						this.insertAndKeepEl(newLine, text[i], match.type);
					}
				}

				pointer = match.end;
			}, this);

			// Add last unmatched code segment if it exists.
			if (pointer <= this.code.length) {
				text = this.code.substring(pointer, this.code.length).split('\n');
				for (var i = 0; i < text.length; i++) {
					newLine = this.insertAndMakeEl(newLine, lighter, text[i], child);
					if (addLines) { lineNum = this.insertLineNum(newLine, lineNum, numType); }
				}
			}

			// Add alternate line styles based on pseudo-selector.
			if (this.options.altLines !== '') {
				if (this.options.altLines == 'hover') {
					lighter.getElements('.'+this.options.flame+'line').addEvents({
							'mouseover': function() {this.toggleClass('alt');},
							'mouseout':  function() {this.toggleClass('alt');}
					});
				} else {
					if (child[1]) {
						lighter.getChildren(':'+this.options.altLines).getElement('.'+this.options.flame+'line').addClass('alt');
					} else {
						lighter.getChildren(':'+this.options.altLines).addClass('alt');
					}
				}
			}

			// Add first/last line classes to correct element based on mode.
			if (child[1]) {
				lighter.getFirst().getChildren().addClass(this.options.flame+'first');
				lighter.getLast().getChildren().addClass(this.options.flame+'last');
			} else {
				lighter.getFirst().addClass(this.options.flame+'first');
				lighter.getLast().addClass(this.options.flame+'last');
			}

			// Ensure we return the real parent, not just an inner element like a tbody.
			if (parent[1]) { lighter = lighter.getParent(); }
			return lighter;
		},

		/** Helper function to insert new code segment into existing line. */
		insertAndKeepEl: function(el, text, alias) {
			if (text.length > 0) {
				var span = new Element('span', { 'text': text });
				if (alias) {
					span.addClass(this.fuel.aliases[alias] || alias);
				}
				span.inject(el);
			}
		},

		/** Helper function to insert new code segment into existing line and create new line. */
		insertAndMakeEl: function(el, group, text, child, alias) {
			this.insertAndKeepEl(el, text, alias);
			if (child[1]) { el = el.getParent(); }
			el.inject(group);

			var newLine = new Element(child[0]);
			if (child[1]) { newLine = new Element(child[1]).inject(newLine); }
			newLine.addClass(this.options.flame+'line');
			return newLine;
		},

		/** Helper funciton to insert line number into line. */
		insertLineNum: function(el, lineNum, elType) {
			var newNum = new Element(elType, {
				'text':  lineNum++,
				'class': this.options.flame+ 'num'
			});
			newNum.inject(el.getParent(), 'top');

			return lineNum;
		},

		/* ----------------------------------- */
		/* ------>>> toType METHODS <<<------- */
		/* ----------------------------------- */
		toElement: function() {
			if (!this.element) {
				this.element = this.builder[this.options.mode]();
				if (this.options.editable) { this.element.set('contenteditable', 'true'); }
			}

			return this.element;
		},

		toString: function() {
			return this.code;
		}

	});

	/** Element Native extensions */
	Element.implement({ light: function(options){ return new Lighter(this, options); } });

	/** String functions */
	function chop(str) { return str.replace(/(^\s*\n|\n\s*$)/gi, ''); }
	function tabToSpaces(str, spaces) {
		for (var i = 0, indent = ''; i < spaces; i++) { indent += ' '; }
		return str.replace(/\t/g, indent);
	}

})();


Python 3 support and more

"""memorised module - container for the memorise python-memcache decorator"""
__author__ = 'Wes Mason <wes [at] 1stvamp [dot] org>'
__docformat__ = 'restructuredtext en'
__version__ = '1.0.1'

import memcache
import itertools
from hashlib import md5
from functools import wraps
import inspect

class memorise(object):
        """Decorate any function or class method/staticmethod with a memcace
        enabled caching wrapper. Similar to the memoise pattern, this will push
        mutator operators into memcache.Client.set(), and pull accessor
        operations from memcache.Client.get().
        An MD5 hash of values, such as attributes on the parent instance/class,
        and arguements, is used as a unique key in memcache.

        :Parameters:
          `mc` : memcache.Client
            The memcache client instance to use.
          `mc_servers` : list
            A list of memcache servers to use in the cluster.
          `parent_keys` : list
            A list of attributes in the parent instance or class to use for
            key hashing.
          `set` : string
            An attribute present in the parent instance or class to set
            to the same value as the cached return value. Handy for keeping
            models in line if attributes are accessed directly in other
            places, or for pickling instances.
          `ttl` : integer
            Tells memcached the time which this value should expire.
            We default to 0 == cache forever. None is turn off caching.
          `update` : boolean
            If `invalidate` is False, Refresh ttl value in cache.
            If `invalidate` is True, set the cache value to `value`
          `invalidate` : boolean
            Invalidates key
          `value` : object
            used only if invalidate == True and update == True
            set the cached value to `value`
        """

        def __init__(self, mc=None, mc_servers=None, parent_keys=[], set=None, ttl=0, update=False, invalidate=False, value=None):
                # Instance some default values, and customisations
                self.parent_keys = parent_keys
                self.set = set
                self.ttl = ttl
                self.update = update
                self.invalidate = invalidate
                self.value = value

                if not mc:
                        if not mc_servers:
                                mc_servers = ['localhost:11211']
                        self.mc = memcache.Client(mc_servers, debug=0)
                else:
                        self.mc = mc

        def __call__(self, fn):
                @wraps(fn)
                def wrapper(*args, **kwargs):
                        # Get a list of arguement names from the func_code
                        # attribute on the function/method instance, so we can
                        # test for the presence of self or cls, as decorator
                        # wrapped instances lose frame and no longer contain a
                        # reference to their parent instance/class within this
                        # frame
                        argnames = fn.func_code.co_varnames[:fn.func_code.co_argcount]
                        method = False
                        static = False
                        if len(argnames) > 0:
                                if argnames[0] == 'self' or argnames[0] == 'cls':
                                        method = True
                                        if argnames[0] == 'cls':
                                                static = True

                        arg_values_hash = []
                        # Grab all the keyworded and non-keyworded arguements so
                        # that we can use them in the hashed memcache key
                        for i,v in sorted(itertools.chain(itertools.izip(argnames, args), kwargs.iteritems())):
                                if i != 'self':
                                        if i != 'cls':
                                                arg_values_hash.append("%s=%s" % (i,v))

                        class_name = None
                        if method:
                                keys = []
                                if len(self.parent_keys) > 0:
                                        for key in self.parent_keys:
                                                keys.append("%s=%s" % (key, getattr(args[0], key)))
                                keys = ','.join(keys)
                                if static:
                                # Get the class name from the cls argument
                                        class_name = args[0].__name__
                                else:
                                # Get the class name from the self argument
                                        class_name = args[0].__class__.__name__
                                module_name = inspect.getmodule(args[0]).__name__
                                parent_name = "%s.%s[%s]::" % (module_name, class_name, keys)
                        else:
                                # Function passed in, use the module name as the
                                # parent
                                parent_name = inspect.getmodule(fn).__name__
                        # Create a unique hash of the function/method call
                        key = "%s%s(%s)" % (parent_name, fn.__name__, ",".join(arg_values_hash))
                        key = key.encode('utf8') if type(key)==unicode else key
                        key = md5(key).hexdigest()
                        if self.mc:
                                # Try and get the value from memcache
                                if self.invalidate and self.update:
                                    output = self.value
                                else:
                                    output = (not self.invalidate) and self.mc.get(key)
                                exist = True
                                if not output:
                                        exist = False
                                        # Otherwise get the value from
                                        # the function/method
                                        output = fn(*args, **kwargs)
                                if self.update or not exist:
                                        if output is None:
                                                set_value = memcache_none()
                                        else:
                                                set_value = output
                                        # And push it into memcache
                                        if self.ttl is not None:
                                                self.mc.set(key, set_value, time=self.ttl)
                                        else:
                                                self.mc.set(key, set_value)
                                if output.__class__ is memcache_none:
                                        # Because not-found keys return
                                        # a None value, we use the
                                        # memcache_none stub class to
                                        # detect these, and make a
                                        # distinction between them and
                                        # actual None values
                                        output = None
                                if self.set:
                                        # Set an attribute of the parent
                                        # instance/class to the output value,
                                        # this can help when other code
                                        # accesses attribures directly, or you
                                        # want to pickle the instance
                                        set_attr = getattr(fn.__class__, self.set)
                                        set_attr = output

                        else :
                                # No memcache client instance available, just
                                # return the output of the method
                                output = fn(*args, **kwargs)
                        return output
                return wrapper

class memcache_none:
        """Stub class for storing None values in memcache,
        so we can distinguish between None values and not-found
        entries.
        """
        pass

if __name__ == '__main__':
        # Run unit tests
        from memorised import tests
        tests.run()

from memorised.decorators import memorise
from memorised.utils import uncache
import unittest
import memcache
import uuid

def unique():
        return str(uuid.uuid4())

class TestModel:
        c = None
        d = None
        def __init__(self):
                self.a = None
                self.b = None
                self.id = 1

        def set_a(self, a):
                self.a = a

        @memorise(parent_keys=['id'])
        def get_a(self):
                return self.a

        def set_b(self, b):
                self.b = b

        def get_b(self):
                return self.b

        @classmethod
        def set_c(cls, c):
                cls.c = c

        @classmethod
        @memorise()
        def get_c(cls):
                return cls.c

        @classmethod
        def set_d(cls, d):
                cls.d = d

        @classmethod
        def get_d(cls):
                return cls.d

@memorise()
def func_get_a():
        return unique()

def func_get_b():
        return unique()

@memorise()
def func_get_c(foo=None, bar=None):
        return unique()

@memorise()
def func_get_d(bar=None, foo=None):
        return unique()

class TestMemorise(unittest.TestCase):
        def setUp(self):
                self.mc = memcache.Client(['localhost:11211'], debug=0)

        def testsimplefunction(self):
                uncache(func_get_a)()
                a1 = func_get_a()
                a2 = func_get_a()
                self.assertEqual(a1, a2)
                b1 = func_get_b()
                b2 = func_get_b()
                self.assertNotEqual(b1, b2)

        def testinstancemethod(self):
                t = TestModel()
                u = unique()
                t.set_a(u)
                uncache(t.get_a, parent_keys=['id'])()
                self.assertEqual(u, t.get_a())
                t1 = TestModel()
                self.assertEqual(u, t1.get_a())
                t.set_b(u)
                self.assertEqual(u, t.get_b())
                t.set_b(1)
                self.assertEqual(1, t.get_b())

        def testclassmethod(self):
                u = unique()
                TestModel.set_c(u)
                uncache(TestModel.get_c)()
                self.assertEqual(u, TestModel.get_c())
                TestModel.set_c(1)
                self.assertEqual(u, TestModel.get_c())
                TestModel.set_d(u)
                self.assertEqual(u, TestModel.get_d())
                TestModel.set_d(1)
                self.assertNotEqual(u, TestModel.get_d())

        def testkwargs(self):
                uncache(func_get_c)()
                c1 = func_get_c()
                c2 = func_get_c()
                self.assertEqual(c1, c2)
                c3 = func_get_c(foo=1)
                self.assertNotEqual(c1, c3)
                c3 = func_get_c(1, 2)
                c4 = func_get_c(foo=1, bar=2)
                c5 = func_get_c(bar=2, foo=1)
                self.assertEqual(c3, c4)
                self.assertEqual(c3, c5)
                uncache(func_get_d)()
                d1 = func_get_d(1, 2)
                d2 = func_get_d(foo=2, bar=1)
                d3 = func_get_d(bar=1, foo=2)
                self.assertEqual(d1, d2)
                self.assertEqual(d1, d3)

def run():
        print
        print "Running memorised.decorators test suite..."
        print
        suite =        unittest.TestLoader().loadTestsFromTestCase(TestMemorise)
        unittest.TextTestRunner(verbosity=2).run(suite)

if __name__ == '__main__':
        run()

"""memorised module - container for the memorise python-memcache decorator"""
__author__ = 'Wes Mason <wes [at] 1stvamp [dot] org>'
__docformat__ = 'restructuredtext en'
__version__ = '1.0.1'

import memcache
from hashlib import md5
import inspect

def uncache(fn, mc=None, mc_servers=None, parent_keys=[]):
        if not mc:
                if not mc_servers:
                        mc_servers = ['localhost:11211']
                mc = memcache.Client(mc_servers, debug=0)
        def wrapper(*args, **kwargs):
                argnames = fn.func_code.co_varnames[:fn.func_code.co_argcount]
                method = False
                static = False
                if hasattr(fn, 'im_self'):
                        method = True
                        if inspect.isclass(fn.im_self):
                                static = True

                arg_values_hash = []
                # Grab all the keyworded and non-keyworded arguements so
                # that we can use them in the hashed memcache key
                for i,v in (zip(argnames, args) + kwargs.items()):
                        arg_values_hash.append("%s=%s" % (i,v))

                class_name = None
                if method:
                        keys = []
                        if len(parent_keys) > 0:
                                for key in parent_keys:
                                        keys.append("%s=%s" % (key, getattr(fn.im_self, key)))
                        keys = ','.join(keys)
                        # Get the class name from the self argument
                        if inspect.isclass(fn.im_self):
                                class_name = fn.im_self.__name__
                        else:
                                class_name = fn.im_self.__class__.__name__
                        module_name = inspect.getmodule(fn.im_self).__name__
                        parent_name = "%s.%s[%s]::" % (module_name, class_name, keys)
                else:
                        # Function passed in, use the module name as the
                        # parent
                        parent_name = inspect.getmodule(fn).__name__
                # Create a unique hash of the function/method call
                key = "%s%s(%s)" % (parent_name, fn.__name__, ",".join(arg_values_hash))
                key = md5(key).hexdigest()

                if mc:
                        mc.delete(key)
                        return True
                else:
                        return False
        return wrapper

"""Installer for memorised
"""

import os
cwd = os.path.dirname(__file__)

try:
    from setuptools import setup, find_packages
except ImportError:
    from ez_setup import use_setuptools
    use_setuptools()
    from setuptools import setup, find_packages

setup(
        name='memorised',
        version='1.0.5',
        author='Wes Mason',
        author_email='wes@1stvamp.org',
        description='memcache memoization decorators and utils for python',
        long_description=open(os.path.join(cwd, 'README.rst')).read(),
        url='http://github.com/1stvamp/memorised/',
        packages=find_packages(exclude=('ez_setup',)),
        install_requires=('python-memcached',),
        license='BSD'
)

"""
Compatibility utilities for running Python 2 and 3 from the same codebase.
"""

import operator
import sys

PY3 = sys.version_info[0] == 3

if PY3:
    text_type = str

    def iteritems(d, **kw):
        return iter(d.items(**kw))

    izip = zip
    _meth_self = "__self__"
    _func_code = "__code__"
else:
    text_type = unicode

    def iteritems(d, **kw):
        return iter(d.iteritems(**kw))

    from itertools import izip
    _meth_self = "im_self"
    _func_code = "func_code"

get_method_self = operator.attrgetter(_meth_self)
get_function_code = operator.attrgetter(_func_code)

"""memorised module - container for the memorise python-memcache decorator"""
__author__ = 'Wes Mason <wes [at] 1stvamp [dot] org>'
__docformat__ = 'restructuredtext en'
__version__ = '1.0.1'

from functools import wraps
from hashlib import md5
import inspect
import itertools

import memcache

from memorised import compat

class memorise(object):
        """Decorate any function or class method/staticmethod with a memcace
        enabled caching wrapper. Similar to the memoise pattern, this will push
        mutator operators into memcache.Client.set(), and pull accessor
        operations from memcache.Client.get().
        An MD5 hash of values, such as attributes on the parent instance/class,
        and arguements, is used as a unique key in memcache.

        :Parameters:
          `mc` : memcache.Client
            The memcache client instance to use.
          `mc_servers` : list
            A list of memcache servers to use in the cluster.
          `parent_keys` : list
            A list of attributes in the parent instance or class to use for
            key hashing.
          `set` : string
            An attribute present in the parent instance or class to set
            to the same value as the cached return value. Handy for keeping
            models in line if attributes are accessed directly in other
            places, or for pickling instances.
          `ttl` : integer
            Tells memcached the time which this value should expire.
            We default to 0 == cache forever. None is turn off caching.
          `update` : boolean
            If `invalidate` is False, Refresh ttl value in cache.
            If `invalidate` is True, set the cache value to `value`
          `invalidate` : boolean
            Invalidates key
          `value` : object
            used only if invalidate == True and update == True
            set the cached value to `value`
        """

        def __init__(self, mc=None, mc_servers=None, parent_keys=[], set=None, ttl=0, update=False,
                     invalidate=False, value=None):
                # Instance some default values, and customisations
                self.parent_keys = parent_keys
                self.set = set
                self.ttl = ttl
                self.update = update
                self.invalidate = invalidate
                self.value = value

                if not mc:
                        if not mc_servers:
                                mc_servers = ['localhost:11211']
                        self.mc = memcache.Client(mc_servers, debug=0)
                else:
                        self.mc = mc

        def __call__(self, fn):
                @wraps(fn)
                def wrapper(*args, **kwargs):
                        key = self.key(fn, args, kwargs)
                        if self.mc:
                                # Try and get the value from memcache
                                if self.invalidate and self.update:
                                    output = self.value
                                else:
                                    output = (not self.invalidate) and self.get_cache(key)
                                exist = True
                                if output is None:
                                        exist = False
                                        # Otherwise get the value from
                                        # the function/method
                                        output = self.call_function(fn, args, kwargs)
                                if self.update or not exist:
                                        if output is None:
                                                set_value = memcache_none()
                                        else:
                                                set_value = output
                                        self.set_cache(key, set_value)
                                if output.__class__ is memcache_none:
                                        # Because not-found keys return
                                        # a None value, we use the
                                        # memcache_none stub class to
                                        # detect these, and make a
                                        # distinction between them and
                                        # actual None values
                                        output = None
                                if self.set:
                                        # Set an attribute of the parent
                                        # instance/class to the output value,
                                        # this can help when other code
                                        # accesses attribures directly, or you
                                        # want to pickle the instance
                                        set_attr = getattr(fn.__class__, self.set)
                                        set_attr = output

                        else:
                                # No memcache client instance available, just
                                # return the output of the method
                                output = self.call_function(fn, args, kwargs)
                        return output
                return wrapper

        def call_function(self, fn, args, kwargs):
            return fn(*args, **kwargs)

        def key(self, fn, args, kwargs):
                # Get a list of arguement names from the func_code
                # attribute on the function/method instance, so we can
                # test for the presence of self or cls, as decorator
                # wrapped instances lose frame and no longer contain a
                # reference to their parent instance/class within this
                # frame
                func_code = compat.get_function_code(fn)
                argnames = func_code.co_varnames[:func_code.co_argcount]
                method = False
                static = False
                if len(argnames) > 0:
                        if argnames[0] == 'self' or argnames[0] == 'cls':
                                method = True
                                if argnames[0] == 'cls':
                                        static = True

                arg_values_hash = []
                # Grab all the keyworded and non-keyworded arguements so
                # that we can use them in the hashed memcache key
                for i, v in sorted(itertools.chain(compat.izip(argnames, args),
                                                   compat.iteritems(kwargs))):
                        if i != 'self':
                                if i != 'cls':
                                        arg_values_hash.append("%s=%s" % (i, v))

                class_name = None
                if method:
                        keys = []
                        if len(self.parent_keys) > 0:
                                for key in self.parent_keys:
                                        keys.append("%s=%s" % (key, getattr(args[0], key)))
                        keys = ','.join(keys)
                        if static:
                                # Get the class name from the cls argument
                                class_name = args[0].__name__
                        else:
                                # Get the class name from the self argument
                                class_name = args[0].__class__.__name__
                        module_name = inspect.getmodule(args[0]).__name__
                        parent_name = "%s.%s[%s]::" % (module_name, class_name, keys)
                else:
                        # Function passed in, use the module name as the
                        # parent
                        parent_name = inspect.getmodule(fn).__name__
                # Create a unique hash of the function/method call
                key = "%s%s(%s)" % (parent_name, fn.__name__, ",".join(arg_values_hash))
                key = key.encode('utf8') if isinstance(key, compat.text_type) else key
                key = md5(key).hexdigest()
                return key

        def get_cache(self, key):
            return self.mc.get(key)

        def set_cache(self, key, value):
            if self.ttl is not None:
                    self.mc.set(key, value, time=self.ttl)
            else:
                    self.mc.set(key, value)

class memcache_none:
        """Stub class for storing None values in memcache,
        so we can distinguish between None values and not-found
        entries.
        """
        pass

if __name__ == '__main__':
        # Run unit tests
        from memorised import tests
        tests.run()

# -*- coding: utf-8 -*-

from __future__ import unicode_literals
import unittest
import uuid

import memcache

from memorised.decorators import memorise, memcache_none

class instrumented_memorise(memorise):
        """
        A version of the memorise decorator that records the number of cache
        and function calls it makes.
        """

        def __init__(self, *args, **kwargs):
                super(instrumented_memorise, self).__init__(*args, **kwargs)
                self.reset()

        def __call__(self, *args, **kwargs):
                func = super(instrumented_memorise, self).__call__(*args, **kwargs)
                func.mem = self
                return func

        def reset(self):
                self.get_calls = 0
                self.set_calls = 0
                self.function_calls = 0

        def call_function(self, *args, **kwargs):
                value = super(instrumented_memorise, self).call_function(*args, **kwargs)
                self.function_calls += 1
                return value

        def get_cache(self, *args, **kwargs):
                value = super(instrumented_memorise, self).get_cache(*args, **kwargs)
                self.get_calls += 1
                return value

        def set_cache(self, *args, **kwargs):
                value = super(instrumented_memorise, self).set_cache(*args, **kwargs)
                self.set_calls += 1
                return value

def unique():
        return str(uuid.uuid4())

class Model:
        c = None
        d = None

        def __init__(self):
                self.a = None
                self.b = None
                self.id = 1

        def set_a(self, a):
                self.a = a

        @memorise(parent_keys=['id'])
        def get_a(self):
                return self.a

        def set_b(self, b):
                self.b = b

        def get_b(self):
                return self.b

        @classmethod
        def set_c(cls, c):
                cls.c = c

        @classmethod
        @memorise()
        def get_c(cls):
                return cls.c

        @classmethod
        def set_d(cls, d):
                cls.d = d

        @classmethod
        def get_d(cls):
                return cls.d

@memorise()
def func_get_a():
        return unique()

def func_get_b():
        return unique()

@memorise()
def func_get_c(foo=None, bar=None):
        return unique()

@memorise()
def func_get_d(bar=None, foo=None):
        return unique()

@memorise()
def func_unicode():
        return '§'

@instrumented_memorise()
def func_none():
        return None

@instrumented_memorise()
def func_zero():
        return 0

@instrumented_memorise()
def func_nonzero():
        return 1

class TestMemorise(unittest.TestCase):
        def setUp(self):
                self.mc = memcache.Client(['localhost:11211'], debug=0)
                self.mc.flush_all()

        def testsimplefunction(self):
                a1 = func_get_a()
                a2 = func_get_a()
                self.assertEqual(a1, a2)
                b1 = func_get_b()
                b2 = func_get_b()
                self.assertNotEqual(b1, b2)

        def testinstancemethod(self):
                t = Model()
                u = unique()
                t.set_a(u)
                self.assertEqual(u, t.get_a())
                t1 = Model()
                self.assertEqual(u, t1.get_a())
                t.set_b(u)
                self.assertEqual(u, t.get_b())
                t.set_b(1)
                self.assertEqual(1, t.get_b())

        def testclassmethod(self):
                u = unique()
                Model.set_c(u)
                self.assertEqual(u, Model.get_c())
                Model.set_c(1)
                self.assertEqual(u, Model.get_c())
                Model.set_d(u)
                self.assertEqual(u, Model.get_d())
                Model.set_d(1)
                self.assertNotEqual(u, Model.get_d())

        def testkwargs(self):
                c1 = func_get_c()
                c2 = func_get_c()
                self.assertEqual(c1, c2)
                c3 = func_get_c(foo=1)
                self.assertNotEqual(c1, c3)
                c3 = func_get_c(1, 2)
                c4 = func_get_c(foo=1, bar=2)
                c5 = func_get_c(bar=2, foo=1)
                self.assertEqual(c3, c4)
                self.assertEqual(c3, c5)
                d1 = func_get_d(1, 2)
                d2 = func_get_d(foo=2, bar=1)
                d3 = func_get_d(bar=1, foo=2)
                self.assertEqual(d1, d2)
                self.assertEqual(d1, d3)

        def test_return_value_unicode(self):
                f = func_unicode
                value = '\u00A7'  # § (section) symbol.
                a1 = f()
                self.assertEqual(a1, value)
                a2 = f()
                self.assertEqual(a1, a2)

        def test_return_value_none(self):
                f = func_none
                f.mem.reset()
                self.assertEqual(f.mem.function_calls, 0)
                a1 = f()
                self.assertEqual(f.mem.function_calls, 1)
                # The function should give us back None, and the cache should
                # contain the memcache_none sentinal value.
                self.assertEqual(a1, None)
                self.assertTrue(isinstance(f.mem.get_cache(f.mem.key(f, (), {})), memcache_none))
                a2 = f()
                self.assertEqual(f.mem.function_calls, 1)
                self.assertEqual(a1, a2)

        def test_return_value_zero(self):
                f = func_zero
                f.mem.reset()
                self.assertEqual(f.mem.function_calls, 0)
                a1 = f()
                self.assertEqual(f.mem.function_calls, 1)
                self.assertEqual(a1, 0)
                self.assertEqual(f.mem.get_cache(f.mem.key(f, (), {})), 0)
                a2 = f()
                self.assertEqual(f.mem.function_calls, 1)
                self.assertEqual(a1, a2)

        def test_return_value_nonzero(self):
                f = func_nonzero
                f.mem.reset()
                self.assertEqual(f.mem.function_calls, 0)
                a1 = f()
                self.assertEqual(f.mem.function_calls, 1)
                self.assertEqual(a1, 1)
                self.assertEqual(f.mem.get_cache(f.mem.key(f, (), {})), 1)
                a2 = f()
                self.assertEqual(f.mem.function_calls, 1)
                self.assertEqual(a1, a2)

def run():
        print()
        print("Running memorised.decorators test suite...")
        print()
        suite = unittest.TestLoader().loadTestsFromTestCase(TestMemorise)
        unittest.TextTestRunner(verbosity=2).run(suite)

if __name__ == '__main__':
        run()

"""Installer for memorised
"""

import os
cwd = os.path.dirname(__file__)

try:
    from setuptools import setup, find_packages
except ImportError:
    from ez_setup import use_setuptools
    use_setuptools()
    from setuptools import setup, find_packages

from memorised import compat

# python-memcached does not have a release compatible with Python 3,
# so use fork: https://github.com/eguven/python3-memcached
if compat.PY3:
    install_requires = ('python3-memcached',)
else:
    install_requires = ('python-memcached',)

setup(
        name='memorised',
        version='1.0.5',
        author='Wes Mason',
        author_email='wes@1stvamp.org',
        description='memcache memoization decorators and utils for python',
        long_description=open(os.path.join(cwd, 'README.rst')).read(),
        url='http://github.com/1stvamp/memorised/',
        packages=find_packages(exclude=('ez_setup',)),
        install_requires=install_requires,
        license='BSD',
        classifiers = [
                'Programming Language :: Python',
                'Programming Language :: Python :: 2',
                'Programming Language :: Python :: 2.6',
                'Programming Language :: Python :: 2.7',
                'Programming Language :: Python :: 3',
                'Programming Language :: Python :: 3.2',
                'Programming Language :: Python :: 3.3',
                'Programming Language :: Python :: 3.4',
        ],
)