Index: edge.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/edge/edge.module,v
retrieving revision 1.1
diff -u -p -r1.1 edge.module
--- edge.module	2 Oct 2010 00:44:47 -0000	1.1
+++ edge.module	2 Oct 2010 22:47:53 -0000
@@ -6,3 +6,27 @@
  * Cutting Edge functionality.
  */
 
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function edge_theme_registry_alter(&$callbacks) {
+  $path = drupal_get_path('module', 'edge');
+
+  // Apply replacement theme callbacks, but only if they have not been
+  // overridden.
+  $edge_theme = array('item_list');
+  foreach ($edge_theme as $callback) {
+    if (isset($callbacks[$callback]) && $callbacks[$callback]['function'] == 'theme_' . $callback) {
+      $callbacks[$callback]['function'] = 'theme_edge_' . $callback;
+      $callbacks[$callback]['includes'][] = $path . '/edge.theme.inc';
+      $callbacks[$callback]['file'] = 'edge.theme.inc';
+
+      // Perform additional callback-specific tweaks, if necessary.
+      switch ($callback) {
+        case 'item_list':
+          $callbacks[$callback]['variables']['title'] = '';
+          break;
+      }
+    }
+  }
+}
Index: edge.theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/edge/edge.theme.inc,v
retrieving revision 1.1
diff -u -p -r1.1 edge.theme.inc
--- edge.theme.inc	2 Oct 2010 00:44:47 -0000	1.1
+++ edge.theme.inc	3 Oct 2010 00:12:08 -0000
@@ -6,3 +6,88 @@
  * Cutting Edge theme functions.
  */
 
+/**
+ * Returns HTML for a list or nested list of items.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - items: A list of items to render. String values are rendered as is. Each
+ *     item can also be an associative array containing:
+ *     - data: The string content of the list item.
+ *     - children: A list of nested child items to render.
+ *     - ...: All other key/value pairs are used as HTML attributes for the list
+ *       item and derived to nested child items, if any.
+ *   - title: The title of the list.
+ *   - type: The type of list to return (e.g. "ul", "ol").
+ *   - attributes: The attributes applied to the list element.
+ */
+function theme_edge_item_list($variables) {
+  $items = $variables['items'];
+  $title = $variables['title'];
+  $type = $variables['type'];
+  $list_attributes = $variables['attributes'];
+
+  $output = '';
+  if ($items) {
+    $output .= '<' . $type . drupal_attributes($list_attributes) . '>';
+
+    $num_items = count($items);
+    $i = 0;
+    foreach ($items as $key => $item) {
+      $i++;
+      $attributes = array();
+
+      if (is_array($item)) {
+        $value = '';
+        if (isset($item['data'])) {
+          $value .= $item['data'];
+        }
+        $attributes = array_diff_key($item, array('data' => 0, 'children' => 0));
+
+        // Append nested child list, if any.
+        if (isset($item['children'])) {
+          // List attributes are normally defined through the 'attributes' variable,
+          // but for nested child lists, all non-numeric keys in 'children' must be
+          // treated as child list attributes.
+          $child_list_attributes = array();
+          foreach ($item['children'] as $child_key => $child_item) {
+            if (is_string($child_key)) {
+              $child_list_attributes[$child_key] = $child_item;
+              unset($item['children'][$child_key]);
+            }
+          }
+          $value .= theme('item_list', array(
+            'items' => $item['children'],
+            'type' => $type,
+            'attributes' => $child_list_attributes,
+          ));
+        }
+      }
+      else {
+        $value = $item;
+      }
+
+      $attributes['class'][] = ($i % 2 ? 'odd' : 'even');
+      if ($i == 1) {
+        $attributes['class'][] = 'first';
+      }
+      if ($i == $num_items) {
+        $attributes['class'][] = 'last';
+      }
+
+      $output .= '<li' . drupal_attributes($attributes) . '>' . $value . '</li>';
+    }
+    $output .= "</$type>";
+  }
+
+  // Only output the list container and title, if there are any list items.
+  if ($output !== '') {
+    if ($title !== '') {
+      $title = '<h3>' . $title . '</h3>';
+    }
+    $output = '<div class="item-list">' . $title . $output . '</div>';
+  }
+
+  return $output;
+}
+
Index: tests/edge.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/edge/tests/edge.test,v
retrieving revision 1.1
diff -u -p -r1.1 edge.test
--- tests/edge.test	2 Oct 2010 00:44:47 -0000	1.1
+++ tests/edge.test	2 Oct 2010 23:12:55 -0000
@@ -19,6 +19,52 @@ class EdgeThemeUnitTest extends DrupalUn
   }
 
   /**
+   * Tests theme_edge_item_list().
+   */
+  function testItemList() {
+    // Verify that empty variables produce no output.
+    $variables = array();
+    $expected = '';
+    $this->assertThemeOutput('item_list', $variables, $expected);
+
+    // Verify nested item lists.
+    $variables = array();
+    $variables['items'] = array(
+      'a',
+      array(
+        'data' => 'b',
+        'children' => array(
+          'c',
+          // Nested children may use additional attributes.
+          array(
+            'data' => 'd',
+            'class' => array('dee'),
+          ),
+          // Any string key is treated as child list attribute.
+          'id' => 'childlist',
+        ),
+        // Any other keys are treated as item attributes.
+        'id' => 'bee',
+      ),
+      array(
+        'data' => 'e',
+        'id' => 'E',
+      ),
+    );
+    $inner = '<div class="item-list"><ul id="childlist">';
+    $inner .= '<li class="odd first">c</li>';
+    $inner .= '<li class="dee even last">d</li>';
+    $inner .= '</ul></div>';
+
+    $expected = '<div class="item-list"><ul>';
+    $expected .= '<li class="odd first">a</li>';
+    $expected .= '<li id="bee" class="even">b' . $inner . '</li>';
+    $expected .= '<li id="E" class="odd last">e</li></ul></div>';
+
+    $this->assertThemeOutput('item_list', $variables, $expected);
+  }
+
+  /**
    * Asserts themed output.
    *
    * @param $callback
