Index: trunk/Nebulous/Build.PL
===================================================================
--- trunk/Nebulous/Build.PL	(revision 16184)
+++ trunk/Nebulous/Build.PL	(revision 16203)
@@ -80,15 +80,16 @@
     create_makefile_pl  => 'passthrough',
     requires            => {
+        'Class::Accessor::Fast' => 0,
         'Config::YAML'          => '1.42',
+        'DBD::mysql'            => '3.0007',
+        'DBI'                   => '1.53',
+        'File::ExtAttr'         => '1.03',
+        'File::Spec::Functions' => 0,
+        'Log::Log4perl'         => '0.48',
+        'Net::Server::Daemonize'=> '0.05',
+        'Params::Validate'      => '0.73',
+        'SOAP::Lite'            => '0.69',
+        'Sys::Statistics::Linux::DiskUsage' => '0.02',
         'URI'                   => '1.30',
-        'SOAP::Lite'            => '0.69',
-        'Log::Log4perl'         => '0.48',
-        'DBI'                   => '1.53',
-        'DBD::mysql'            => '3.0007',
-        'Class::Accessor::Fast' => 0,
-        'Params::Validate'      => '0.73',
-        'Net::Server::Daemonize'=> '0.05',
-        'Sys::Statistics::Linux::DiskUsage' => '0.02',
-        'File::ExtAttr'         => '1.03',
     },
     build_requires      => {
Index: trunk/Nebulous/Changes
===================================================================
--- trunk/Nebulous/Changes	(revision 16184)
+++ trunk/Nebulous/Changes	(revision 16203)
@@ -2,4 +2,5 @@
 
 0.06
+    - add Nebulous::Util::parse_neb_key()
     - change bin/* scripts to conistently use --volume instead of --node,
       etc.
Index: trunk/Nebulous/MANIFEST
===================================================================
--- trunk/Nebulous/MANIFEST	(revision 16184)
+++ trunk/Nebulous/MANIFEST	(revision 16203)
@@ -110,4 +110,5 @@
 t/63_client_stat.t
 t/64_client_find_objects.t
+t/75_parse_neb_key.t
 t/90_nebclient.t
 t/TEST.PL
Index: trunk/Nebulous/lib/Nebulous/Util.pm
===================================================================
--- trunk/Nebulous/lib/Nebulous/Util.pm	(revision 16184)
+++ trunk/Nebulous/lib/Nebulous/Util.pm	(revision 16203)
@@ -1,5 +1,5 @@
 # Copyright (c) 2004  Joshua Hoblitt
 #
-# $Id: Util.pm,v 1.7 2007-05-08 02:27:42 jhoblitt Exp $
+# $Id: Util.pm,v 1.8 2008-01-23 22:27:51 jhoblitt Exp $
 
 package Nebulous::Util;
@@ -12,4 +12,5 @@
 use base qw( Exporter );
 
+use File::Spec::Functions;
 use Log::Log4perl qw( :levels );
 use URI::file;
@@ -22,4 +23,5 @@
     _get_filehandle
     _open_uri
+    parse_neb_key
 );
 
@@ -78,4 +80,49 @@
 }
 
+sub parse_neb_key
+{
+    my $text = shift;
+
+    # white space is not allowed
+    if ($text =~ qr/\s+/) {
+        die "keys and URIs may not contain whitespace";
+    }
+
+    my $uri = URI->new($text);
+    my $scheme = $uri->scheme;
+    my $path = $uri->opaque;
+    
+    my $volume;
+    # if this is a valid uri 
+    if (defined $scheme) {
+        # if so, does it use the neb scheme?
+        die "URI does not use the 'neb' scheme"
+            unless $scheme eq 'neb'; 
+
+        # neb:path (not allowed)
+        # neb://<volume name>/...
+        # neb:/path... (leading '/' is stripped)
+        # neb:///path... (same as neb:/path)
+
+        # volume specifier must be at least one character
+        # strip off volume component if it exists
+        $path =~ s|^//([^/]+)||;
+        $volume = $1;
+
+        # require a leading slash if there is no volume name
+        if ((not defined $volume) and (not $path =~ m|^/|)) {
+            die "neb URI scheme requires a leading slash, eg. neb:/";
+        }
+    }
+
+    # strip leading slashes
+    $path =~ s|^/+||;
+
+    # remove multiple /'s and trailing slashes
+    $path = canonpath($path);
+
+    return ($path, $volume);
+}
+
 1;
 
Index: trunk/Nebulous/t/75_parse_neb_key.t
===================================================================
--- trunk/Nebulous/t/75_parse_neb_key.t	(revision 16203)
+++ trunk/Nebulous/t/75_parse_neb_key.t	(revision 16203)
@@ -0,0 +1,170 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 75_parse_neb_key.t,v 1.1 2008-01-23 22:27:51 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More;
+
+plan tests => 36;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Util qw( :standard );
+use Test::Nebulous;
+
+# neb://<volume name>/...
+# neb:path...
+# neb:/path... (same as neb:path, leading '/' is stripped)
+# neb:///path... (same as neb:path)
+
+# key
+{
+    my ($path, $volume) = parse_neb_key('foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('/foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('//foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('///foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('foo////bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('foo/bar/baz/quix/');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+
+# URI w/ volume name
+{
+    my ($path, $volume) = parse_neb_key('neb://foo/bar/baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('neb://foo//bar/baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('neb://foo///bar/baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('neb://foo/bar///baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('neb://foo/bar/baz/quix/');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+
+# URI w/o volume name
+
+{
+    my ($path, $volume) = parse_neb_key('neb:/foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('neb:///foo/bar/baz/quix');
+
+    is($volume, undef, 'volume name');
+    is($path, 'foo/bar/baz/quix', 'path');
+}
+
+{
+    my ($path, $volume) = parse_neb_key('neb://///foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+# key w/ whitespace
+eval {
+    parse_neb_key('/ foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key(' /foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key('/foo/bar/baz/quix ');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+# URI w/ whitespace
+
+eval {
+    parse_neb_key('neb ://foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key('neb:// foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key(' neb://foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key('neb://foo/bar/baz/quix ');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+# URI w/o volume requires leading slash
+eval {
+    my ($path, $volume) = parse_neb_key('neb:foo/bar/baz/quix');
+};
+like( $@, qr/requires a leading slash/, "leading slash" );
