<?php
/*
Plugin Name: Illudium PU-36 Explosive Codeolulator
Version: 1.0.2
Plugin URI: http://rephrase.net/box/word/codeolulator/
Description: Converts &lt;pre&gt;&lt;code&gt; blocks into pretty ordered lists
Author: Sam Angove
Author URI: http://rephrase.net/
*/

/*
Changelog: 

1.0.2:
* wrap in a class
* use proper wp query methods
* look for code.php in templates

1.0.1: 
* rewrite rules generated better
* don't add "view/download" link for really short code blocks
* code without a number means show all code snippets

Credits: 

As far as I know, the original idea was Simon Willison's:
   <http://simon.incutio.com/archive/2002/07/10/numberedCodeListing>
   
Part of this plugin's code -- an irritatingly small amount, actually; just
the stuff for counting whitespace. I'd have had to reinvent the wheel less
if I'd found it earlier -- was taken straight from Aaron Schaefer's Code Viewer.
If you want code samples kept in a separate location, use that instead.

   <http://elasticdog.com/2004/09/code-viewer/>
*/ 


/****** 
  Options
     ******/ 

/* Show a "View/Download" link to plaintext version? */
$codeolulator['show_download_link'] = true;

/* Code snippets less than this many lines long will not
   get a "View/Download" link attached to them. */
$codeolulator['download_link_minimum_length'] = 4;

/* How many spaces makes one tab in your code samples? */
$codeolulator['tab_length'] = 4;

/* "View/Download" link. 
   Is passed through sprintf(), with %s replaced by the link. */
$codeolulator['download_link_text'] = '<a href="%s">View/Download</a>';


class Codeolulator {
	
	function register_query_var($vars) {
		$vars[] = 'code';
		return $vars;
	}
	
	function post_filter($text) {
		$code = get_query_var('code');
		if (empty($code) && strpos($text, '<pre>') !== false) {
			$text = preg_replace_callback("#<pre>\\s*<code>(.*?)</code>\\s*</pre>#ms", array('Codeolulator', 'post_filter_callback'), $text);
		}
		return $text;
	}
	
	function post_filter_callback($matches) {
		global $codeolulator;
		
		static $code_snippet_number = 0;
		$code_snippet_number++;   
		
		$list = '<ol class="codelist">';

		$lines = preg_split("#\n(?!\\z)#", $matches[1]);
		
		foreach ($lines as $number => $line) {
			$type = ($number % 2 == 1) ? 'even' : 'odd';			
			
			// convert spaces to tabs
			$line = str_replace(str_repeat(' ', $codeolulator['tab_length']), "\t", $line);
						
			// If the line is blank, insert a space to prevent collapsing,
			// otherwise insert the line.
			if (ltrim($line) == "") {
				$list .= "\t" . '<li class="' . $type . '">&nbsp;</li>' . "\n";
			} else {
				$numtabs = strlen($line) - strlen(ltrim($line));  // determine the number of tabs
				$line = trim($line);                              // trim leading/trailing whitespace
	
				$list .= "\t" . '<li class="tab' . $numtabs . ' ' . $type . '"><code>' . $line . '</code></li>' . "\n";
			}
			
		}
		$list .= "</ol>\n";

		
		if ($codeolulator['show_download_link'] && count($lines) > $codeolulator['download_link_minimum_length']) {
			$link = get_permalink();
			
			if (false === strpos($link, '?'))
				$link = trailingslashit($link) . 'code/' . $code_snippet_number;
			else
				$link .= '&code=' . $code_snippet_number;
	
			$list .= '<div class="sourcelink">' . sprintf($codeolulator['download_link_text'], $link) . "</div>\n";
		}
		
		$list = apply_filters('codeolulator_list', $list);
		return $list;
		
	}
	
	function show_code_snippets() {
		$code = get_query_var('code');
		
		if (!empty($code)) {
			
			if ( file_exists( TEMPLATEPATH . '/code.php') ) {
				load_template( TEMPLATEPATH . '/code.php');
				exit;
			}
			
			@header('Content-type: text/plain; charset=utf-8');
			$snippets = array();
			
			// This is overly elaborate for single posts, but I figured I might as well
			// make it support multiple posts from the start.
			if (have_posts()) :
				while (have_posts()) : the_post();
					ob_start();
						the_content();
						$content = ob_get_contents();
					ob_end_clean();
					
					preg_match_all("#<pre>\\s*<code>(.*?)</code>\\s*</pre>#s", html_entity_decode($content), $matches, PREG_PATTERN_ORDER);
					
					if (isset($matches[1])) $snippets = array_merge($snippets, $matches[1]);
							
				endwhile;
			endif;
			
			if ($code == 0)
				// don't use implode, sometimes there's only one
				foreach ($snippets as $snippet) {
					echo "$snippet\n\n";
				}
			else
				echo $snippets[$code-1];

			exit;
		}
	}

	function generate_rewrite($wp_rewrite) {
		// (?:code/)?(code/?|\d+) doesn't work on apache 1.3?
		// Make the regex stupid.
		$wp_rewrite->add_rewrite_tag('%code%', 'code/([0-9]+)', "code=");
		$structure = trailingslashit($wp_rewrite->permalink_structure) . "%code%";
		$rules = $wp_rewrite->generate_rewrite_rule($structure);
		foreach ($rules as $match => $redirect) {
			$match = str_replace(array('(/[0-9]+)?/?$', '/trackback/?$'), '$', $match);
			$redirect = preg_replace(array("#&tb=1#", "#&page=.[0-9]+#"), '', $redirect);
			$newrules[$match] = $redirect;
		}
		$wp_rewrite->rules += $newrules;
	}
}



add_action('generate_rewrite_rules',  array('Codeolulator', 'generate_rewrite'));
add_action('template_redirect', array('Codeolulator', 'show_code_snippets'));

add_filter('query_vars', array('Codeolulator', 'register_query_var'));
add_filter('the_content', array('Codeolulator', 'post_filter'), 9); 

?>