Thread: [Module::Build] version.pm and version number comparison problem
Status: Beta
Brought to you by:
kwilliams
|
From: Ron S. <ro...@sa...> - 2006-02-02 05:26:11
|
Hi Folks
Just wondering:
I'm using version V 0.53 and Module::Build V 0.2611.
When I try to install SQL::Translator (for example) I get:
Checking whether your kit is complete...
Looks good
* Version v1.0.0 of Text::RecordParser is installed, but we prefer to have 0.02
ERRORS/WARNINGS FOUND IN PREREQUISITES. You may wish to install the versions
of the modules indicated above before proceeding with this installation.
The Build.PL for SQL::Translator says:
recommends => {
'GD' => 0,
'GraphViz' => 0,
'IO::File' => 0,
'IO::Scalar' => 0,
'Spreadsheet::ParseExcel' => 0.2602,
'Text::ParseWords' => 0,
'Text::RecordParser' => 0.02,
'XML::Writer' => 0.500,
'XML::XPath' => 1.13,
'YAML' => 0.39,
},
and Text::RecordParser reports itself as being v1.0.0 (yes, with the goddam 'v',
as if we needed it :-().
So, I see where the components of the error message (v1.0.0 and 0.02) come from.
My question is: Should this faulty comparison be something which is/can be
handled properly by version.pm, meaning V 1.0.0 really is reported as being >
V 0.02, or is this something we have to live with?
--
Ron Savage
ro...@sa...
http://savage.net.au/index.html
|
|
From: <and...@fr...> - 2006-02-02 09:00:21
|
>>>>> On Thu, 02 Feb 2006 15:06:28 +1100, Ron Savage <ro...@sa...> said:
> Hi Folks
> Just wondering:
> I'm using version V 0.53 and Module::Build V 0.2611.
> When I try to install SQL::Translator (for example) I get:
> Checking whether your kit is complete...
> Looks good
> * Version v1.0.0 of Text::RecordParser is installed, but we prefer to have 0.02
> ERRORS/WARNINGS FOUND IN PREREQUISITES. You may wish to install the versions
> of the modules indicated above before proceeding with this installation.
> The Build.PL for SQL::Translator says:
> recommends => {
> 'GD' => 0,
> 'GraphViz' => 0,
> 'IO::File' => 0,
> 'IO::Scalar' => 0,
> 'Spreadsheet::ParseExcel' => 0.2602,
> 'Text::ParseWords' => 0,
> 'Text::RecordParser' => 0.02,
> 'XML::Writer' => 0.500,
> 'XML::XPath' => 1.13,
> 'YAML' => 0.39,
> },
> and Text::RecordParser reports itself as being v1.0.0 (yes, with the goddam 'v',
> as if we needed it :-().
Nono, the relevant line in Text::RecordParser is
our $VERSION = version->new('1.0.0');
So do not blame the v, it is just version.pm that helps us a little
with overloading.
> So, I see where the components of the error message (v1.0.0 and 0.02) come from.
> My question is: Should this faulty comparison be something which is/can be
> handled properly by version.pm, meaning V 1.0.0 really is reported as being >
> V 0.02, or is this something we have to live with?
I suppose this is a bug in Module::Build, because version.pm can
compare 1.0.0 and 0.02 as you would expect:
% perl -le '
use version 0.53;
print version->new("1.0.0") > 0.02 ? "ok" : "not ok";
'
ok
--
andreas
|
|
From: John P. <jpe...@ro...> - 2006-02-02 14:06:34
|
Ron Savage wrote:
> and Text::RecordParser reports itself as being v1.0.0 (yes, with the goddam 'v',
> as if we needed it :-().
ENOTMYFAULT
Larry has spoken that version objects in Perl6 will be numbers with a leading
'v' (whether or not they are implemented in the same fashion as version.pm).
>
> So, I see where the components of the error message (v1.0.0 and 0.02) come from.
>
> My question is: Should this faulty comparison be something which is/can be
> handled properly by version.pm, meaning V 1.0.0 really is reported as being >
> V 0.02, or is this something we have to live with?
The problem is threefold:
1) Module::Build::ModuleInfo::_evaluate_version_line() currently uses version.pm
(if installed) to eval the $VERSION assignment, but then immediately throws away
the version object in preference to the *stringified* version;
2) Module::Build::Base::compare_versions() performs a trivial comparison (not
using the overloaded version comparison if available) of the two versions, which
will not work if the one version has a leading 'v' and multiple decimals;
3) The developers of Module::Build don't want to require version.pm to be
installed (because it requires XS code and I haven't been able to spend the time
to create a pure Perl implementation).
However, happily, I have a solution (diff vs. Module-Build-0.27_07):
--- lib/Module/Build/ModuleInfo.pm.orig 2006-02-02 08:46:21.000000000 -0500
+++ lib/Module/Build/ModuleInfo.pm 2006-02-02 08:59:23.000000000 -0500
@@ -299,7 +299,7 @@ sub _evaluate_version_line {
warn "Error evaling version line '$eval' in $self->{filename}: $@\n" if $@;
# Unbless it if it's a version.pm object
- $result = "$result" if UNIVERSAL::isa( $result, 'version' );
+ $result = $result->numify if UNIVERSAL::isa( $result, 'version' );
return $result;
}
This will render the version object into the equivalent numeric form, which
*will* compare trivially with non-version objects. All M::B tests pass and more
importantly for you, SQL::Translator now accepts the installed version of
Text::RecordParser.
HTH
John
--
John Peacock
Director of Information Research and Technology
Rowman & Littlefield Publishing Group
4720 Boston Way
Lanham, MD 20706
301-459-3366 x.5010
fax 301-429-5747
|
|
From: Chris D. <ch...@cl...> - 2006-02-02 16:23:00
|
On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
>
> # Unbless it if it's a version.pm object
> - $result = "$result" if UNIVERSAL::isa( $result, 'version' );
> + $result = $result->numify if UNIVERSAL::isa( $result, 'version' );
While we're at it, we should change the
UNIVERSAL::isa( $result, 'version' )
to
eval { $result->isa('version') }
See http://search.cpan.org/dist/UNIVERSAL-can/ for the
justification. :-)
Chris
--
Chris Dolan, Software Developer, Clotho Advanced Media Inc.
608-294-7900, fax 294-7025, 1435 E Main St, Madison WI 53703
vCard: http://www.chrisdolan.net/ChrisDolan.vcf
Clotho Advanced Media, Inc. - Creators of MediaLandscape Software
(http://www.media-landscape.com/) and partners in the revolutionary
Croquet project (http://www.opencroquet.org/)
|
|
From: Ken W. <ke...@ma...> - 2006-02-03 04:10:27
|
On Feb 2, 2006, at 10:22 AM, Chris Dolan wrote:
> On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
>
>>
>> # Unbless it if it's a version.pm object
>> - $result = "$result" if UNIVERSAL::isa( $result, 'version' );
>> + $result = $result->numify if UNIVERSAL::isa( $result, 'version' );
>
> While we're at it, we should change the
> UNIVERSAL::isa( $result, 'version' )
> to
> eval { $result->isa('version') }
That's probably better, but it has its own problems. What if someone's
overloaded isa() object is trying to throw an exception? This will
throw it away.
Personally I think inheritance is a language feature, and we ought to
have a language feature (like blessed() or tied()), not an inheritable
method, to inspect it. But other people are more passionate about this
than me, so I won't argue.
-Ken
|
|
From: Chris D. <ch...@cl...> - 2006-02-03 04:18:19
|
On Feb 2, 2006, at 10:10 PM, Ken Williams wrote:
> On Feb 2, 2006, at 10:22 AM, Chris Dolan wrote:
>
>> On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
>>
>>>
>>> # Unbless it if it's a version.pm object
>>> - $result = "$result" if UNIVERSAL::isa( $result, 'version' );
>>> + $result = $result->numify if UNIVERSAL::isa( $result,
>>> 'version' );
>>
>> While we're at it, we should change the
>> UNIVERSAL::isa( $result, 'version' )
>> to
>> eval { $result->isa('version') }
>
> That's probably better, but it has its own problems. What if
> someone's overloaded isa() object is trying to throw an exception?
> This will throw it away.
Well, if it throws an exception, it's probably not a version
instance, right?
> Personally I think inheritance is a language feature, and we ought
> to have a language feature (like blessed() or tied()), not an
> inheritable method, to inspect it. But other people are more
> passionate about this than me, so I won't argue.
As I've said in another forum, the isa() and can() stuff is just
broken and we can only hope that Perl6 gets it right. I'm grateful
to John Peacock for trying to get version right at least.
Chris
--
Chris Dolan, Software Developer, Clotho Advanced Media Inc.
608-294-7900, fax 294-7025, 1435 E Main St, Madison WI 53703
vCard: http://www.chrisdolan.net/ChrisDolan.vcf
Clotho Advanced Media, Inc. - Creators of MediaLandscape Software
(http://www.media-landscape.com/) and partners in the revolutionary
Croquet project (http://www.opencroquet.org/)
|
|
From: Ken W. <ke...@ma...> - 2006-02-03 04:27:14
|
On Feb 2, 2006, at 10:18 PM, Chris Dolan wrote: > On Feb 2, 2006, at 10:10 PM, Ken Williams wrote: > >> That's probably better, but it has its own problems. What if >> someone's overloaded isa() object is trying to throw an exception? >> This will throw it away. > > Well, if it throws an exception, it's probably not a version instance, > right? Why not? It could be a version instance that overloads isa() for whatever satanic purpose it wants to, and throws an exception, because it's evil and it can. What I really want to know, and what UNIVERSAL::isa() tells me, is whether there's an @ISA-chain up to 'version'. -Ken |
|
From: Chris D. <ch...@cl...> - 2006-02-03 04:52:59
|
On Feb 2, 2006, at 10:26 PM, Ken Williams wrote:
> On Feb 2, 2006, at 10:18 PM, Chris Dolan wrote:
>> Well, if it throws an exception, it's probably not a version
>> instance, right?
>
> Why not? It could be a version instance that overloads isa() for
> whatever satanic purpose it wants to, and throws an exception,
> because it's evil and it can.
I don't think version.pm is actually in league with the devil. :-)
Presumably, version would only override isa() if it really, really
needed to be overridden. Why should we ignore that decision that was
probably made with great care?
> What I really want to know, and what UNIVERSAL::isa() tells me, is
> whether there's an @ISA-chain up to 'version'.
No, that's not right. What you want to know is if the $version is
not a plain scalar and therefore whether it needs to be numify()ed.
isa('version') is a close proxy for that decision.
Sorry to drag this out -- I know you already abstained, Ken -- but it
took *me* a long time to grasp this through a series of emails with
nothingmuch, so I felt that I should share.
Chris
--
Chris Dolan, Software Developer, Clotho Advanced Media Inc.
608-294-7900, fax 294-7025, 1435 E Main St, Madison WI 53703
vCard: http://www.chrisdolan.net/ChrisDolan.vcf
Clotho Advanced Media, Inc. - Creators of MediaLandscape Software
(http://www.media-landscape.com/) and partners in the revolutionary
Croquet project (http://www.opencroquet.org/)
|
|
From: Ken W. <ke...@ma...> - 2006-02-03 16:41:20
|
On Feb 2, 2006, at 10:52 PM, Chris Dolan wrote:
> On Feb 2, 2006, at 10:26 PM, Ken Williams wrote:
>
>> On Feb 2, 2006, at 10:18 PM, Chris Dolan wrote:
>>> Well, if it throws an exception, it's probably not a version
>>> instance, right?
>>
>> Why not? It could be a version instance that overloads isa() for
>> whatever satanic purpose it wants to, and throws an exception,
>> because it's evil and it can.
>
> I don't think version.pm is actually in league with the devil. :-)
I was referring to some possibly strange & unknown class that
subclasses version.pm.
> Presumably, version would only override isa() if it really, really
> needed to be overridden. Why should we ignore that decision that was
> probably made with great care?
Because IMO even if it was made with great care it's wrong. You can't
change perl's notion of what constitutes a subclass even if you do
override isa().
As I published in a review of UNIVERSAL::isa() last night, this is very
different from overriding can(). For can(), the author does have the
ability to override perl's dispatch of method calls via AUTOLOAD(), so
overriding can() is a good idea. For isa(), we can't override perl's
OO inheritance notions, so overriding isa() is a bad idea and will
leave the caller in a state of confusion over who's right, perl or
isa().
> No, that's not right. What you want to know is if the $version is not
> a plain scalar and therefore whether it needs to be numify()ed.
> isa('version') is a close proxy for that decision.
Just because it's not a plain scalar, though, doesn't mean it needs to
be numified. Theoretically someone has written their *own* class for
version objects, independent of version.pm, which they think is going
to handle things their own special way without use of a numify()
method.
In this case we really *do* want to know whether the $version inherits
from version.pm, because then it will have a published numify() method
with known semantics. If it's some other class, we know nothing.
-Ken
|
|
From: John P. <jpe...@ro...> - 2006-02-03 12:17:19
|
Ken Williams wrote:
>> While we're at it, we should change the
>> UNIVERSAL::isa( $result, 'version' )
>> to
>> eval { $result->isa('version') }
>
> That's probably better, but it has its own problems. What if someone's
> overloaded isa() object is trying to throw an exception? This will
> throw it away.
But we aren't talking about some random class. This *only* applies to
version.pm and I can personally guarantee that it will never include an
overloaded isa() method (I know the author).
Just use the original patch I suggested with ->numify() and UNIVERSAL::isa() and
worry about the theoretical best practices someplace else... ;-)
John
--
John Peacock
Director of Information Research and Technology
Rowman & Littlefield Publishing Group
4720 Boston Way
Lanham, MD 20706
301-459-3366 x.5010
fax 301-429-5747
|
|
From: Ken W. <ke...@ma...> - 2006-02-03 16:31:40
|
On Feb 3, 2006, at 6:17 AM, John Peacock wrote:
> Ken Williams wrote:
>>> While we're at it, we should change the
>>> UNIVERSAL::isa( $result, 'version' )
>>> to
>>> eval { $result->isa('version') }
>>
>> That's probably better, but it has its own problems. What if
>> someone's
>> overloaded isa() object is trying to throw an exception? This will
>> throw it away.
>
> But we aren't talking about some random class. This *only* applies to
> version.pm and I can personally guarantee that it will never include an
> overloaded isa() method (I know the author).
>
> Just use the original patch I suggested with ->numify() and
> UNIVERSAL::isa() and
> worry about the theoretical best practices someplace else... ;-)
Yup, that's what I've done.
-Ken
|
|
From: Ken W. <ke...@ma...> - 2006-02-03 04:18:04
|
On Feb 2, 2006, at 10:22 AM, Chris Dolan wrote:
> On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
>
>>
>> # Unbless it if it's a version.pm object
>> - $result = "$result" if UNIVERSAL::isa( $result, 'version' );
>> + $result = $result->numify if UNIVERSAL::isa( $result, 'version' );
>
> While we're at it, we should change the
> UNIVERSAL::isa( $result, 'version' )
> to
> eval { $result->isa('version') }
BTW, I actually tried this, and it broke the t/xs.t tests. I'll leave
the reason why as an interesting exercise for the reader. =)
-Ken
|
|
From: Chris D. <ch...@cl...> - 2006-02-03 04:45:21
|
On Feb 2, 2006, at 10:17 PM, Ken Williams wrote:
> On Feb 2, 2006, at 10:22 AM, Chris Dolan wrote:
>
>> While we're at it, we should change the
>> UNIVERSAL::isa( $result, 'version' )
>> to
>> eval { $result->isa('version') }
>
> BTW, I actually tried this, and it broke the t/xs.t tests. I'll
> leave the reason why as an interesting exercise for the reader. =)
>
> -Ken
[SPOILER BELOW!]
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
It's a false failure. t/xs.t is calling dispatch() and testing $@ to
see if they fail. The die() triggered by isa() inside the eval sets
$@, and the test fails because $@ is set. However, this is because t/
xs.t mistakenly omits wrapping some of the calls to dispatch() in
their own eval{}. The following test (#2 of 22) passes in xs.t:
eval {$mb->dispatch('build')};
is $@, '';
but this test (#20 of 22) erroneously fails:
$mb->dispatch('build');
is $@, '';
So the test is checking $@ from the wrong eval.
The moral of the story is that using best practices help you find
bugs in code. ;-)
Chris
--
Chris Dolan, Software Developer, Clotho Advanced Media Inc.
608-294-7900, fax 294-7025, 1435 E Main St, Madison WI 53703
vCard: http://www.chrisdolan.net/ChrisDolan.vcf
Clotho Advanced Media, Inc. - Creators of MediaLandscape Software
(http://www.media-landscape.com/) and partners in the revolutionary
Croquet project (http://www.opencroquet.org/)
|
|
From: Ken W. <ke...@ma...> - 2006-02-03 04:11:08
|
On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
> However, happily, I have a solution (diff vs. Module-Build-0.27_07):
>
> --- lib/Module/Build/ModuleInfo.pm.orig 2006-02-02 08:46:21.000000000
> -0500
> +++ lib/Module/Build/ModuleInfo.pm 2006-02-02 08:59:23.000000000
> -0500
> @@ -299,7 +299,7 @@ sub _evaluate_version_line {
> warn "Error evaling version line '$eval' in $self->{filename}:
> $@\n" if $@;
>
> # Unbless it if it's a version.pm object
> - $result = "$result" if UNIVERSAL::isa( $result, 'version' );
> + $result = $result->numify if UNIVERSAL::isa( $result, 'version' );
>
> return $result;
> }
That was awfully easy. I wonder why we didn't think of this before. =)
I'll apply.
-Ken
|
|
From: Julian M. <ju...@me...> - 2006-02-02 18:18:19
|
Chris Dolan wrote:
> On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
> > # Unbless it if it's a version.pm object
> > - $result =3D "$result" if UNIVERSAL::isa( $result, 'version' );
> > + $result =3D $result->numify if UNIVERSAL::isa( $result, 'version' );
>
> While we're at it, we should change the
> UNIVERSAL::isa( $result, 'version' )
> to
> eval { $result->isa('version') }
>
> See http://search.cpan.org/dist/UNIVERSAL-can/ for the
> justification. :-)
"This is wrong and you should not do it." -- What kind of justification is=
=20
that??
Why is it wrong to do UNIVERSAL::isa($foo, $type)? This usage is=20
documented in `perldoc UNIVERSAL`, and it works fine, after all.
|
|
From: Rob K. <rob...@gm...> - 2006-02-02 19:37:43
|
On 2/2/06, Julian Mehnle <ju...@me...> wrote:
> Chris Dolan wrote:
> > On Feb 2, 2006, at 8:06 AM, John Peacock wrote:
> > > # Unbless it if it's a version.pm object
> > > - $result =3D "$result" if UNIVERSAL::isa( $result, 'version' );
> > > + $result =3D $result->numify if UNIVERSAL::isa( $result, 'version' =
);
> >
> > While we're at it, we should change the
> > UNIVERSAL::isa( $result, 'version' )
> > to
> > eval { $result->isa('version') }
> >
> > See http://search.cpan.org/dist/UNIVERSAL-can/ for the
> > justification. :-)
>
> "This is wrong and you should not do it." -- What kind of justification i=
s
> that??
>
> Why is it wrong to do UNIVERSAL::isa($foo, $type)? This usage is
> documented in `perldoc UNIVERSAL`, and it works fine, after all.
What if I overload can() in order to correctly report what I support
through AUTOLOAD because I will handle dynamic attributes?
UNIVERSAL::can() fails there.
What if I overload isa() in order to report back a facade object is
really what it's decorating? UNIVERSAL::isa() fails there, too.
This isn't as unreasonable as it sounds. I overload isa() in
PDF::Template in order to use node names instead of class names
because the same class might handle multiple node types.
Rob
|
|
From: John P. <jpe...@ro...> - 2006-02-02 19:44:06
|
Julian Mehnle wrote: > "This is wrong and you should not do it." -- What kind of justification is > that?? The POD has a little more than that (from UNIVERSAL::can): > Some authors call methods in the UNIVERSAL class on potential > invocants as functions, bypassing any possible overriding. This is > wrong and you should not do it. Unfortunately, not everyone heeds > this warning and their bad code can break your good code. The use of UNIVERSAL::isa() and UNIVERSAL::can() as functions means that any class method by those names is rendered moot. It means that you cannot use objects that only instantiate their true class when required (where you might want to have a custom isa() that effectively lies about what class(es) the object belongs to). For example, imagine a polymorph class that takes on the characteristics of whatever class that is tested for (like Test::MockObject for example). I suppose you could suggest that Module::Build load UNIVERSAL::isa (or at least Scalar::Util for the blessed() sub and call isa() as a object method instead). > Why is it wrong to do UNIVERSAL::isa($foo, $type)? This usage is > documented in `perldoc UNIVERSAL`, and it works fine, after all. That POD was written a long time ago and should not be considered to be the only way to think about it. As it turns out, UNIVERSAL::isa (the core code, not the CPAN replacement) was written in a poorly designed way that does not DTRT with some objects that are now in usage. John -- John Peacock Director of Information Research and Technology Rowman & Littlefield Publishing Group 4501 Forbes Boulevard Suite H Lanham, MD 20706 301-459-3366 x.5010 fax 301-429-5748 |
|
From: Julian M. <ju...@me...> - 2006-02-02 21:10:21
|
John Peacock wrote: > Julian Mehnle wrote: > > "This is wrong and you should not do it." -- What kind of > > justification is that?? > > The POD has a little more than that (from UNIVERSAL::can): >=20 > | Some authors call methods in the UNIVERSAL class on potential > | invocants as functions, bypassing any possible overriding. This is > | wrong and you should not do it. Unfortunately, not everyone heeds > | this warning and their bad code can break your good code. That's not exactly explaining the issue well. Rob Kinyon wrote: > > Why is it wrong to do UNIVERSAL::isa($foo, $type)? This usage is > > documented in `perldoc UNIVERSAL`, and it works fine, after all. > > What if I overload can() in order to correctly report what I support > through AUTOLOAD because I will handle dynamic attributes? > UNIVERSAL::can() fails there. > > What if I overload isa() in order to report back a facade object is > really what it's decorating? UNIVERSAL::isa() fails there, too. Thanks for the plausible explanation! John Peacock wrote: > > Why is it wrong to do UNIVERSAL::isa($foo, $type)? This usage is > > documented in `perldoc UNIVERSAL`, and it works fine, after all. > > [The "UNIVERSAL" POD] was written a long time ago and should not be > considered to be the only way to think about it. As it turns out, > UNIVERSAL::isa (the core code, not the CPAN replacement) was written in a > poorly designed way that does not DTRT with some objects that are now in > usage.=20 All that doesn't help much as long as the "UNIVERSAL" POD is part of the=20 official Perl distribution, while the "UNIVERSAL::isa" POD is not. This=20 makes the "UNIVERSAL" POD look authoritative, not the other POD. Besides, the "UNIVERSAL::isa" POD doesn't explain the issue very well, see= =20 above. And why is it titled "Hack around people calling UNIVERSAL::can()=20 as a function" if it is supposed to actually _improve_ code, which is not=20 normally the case with _hacks_? |
|
From: Chris D. <ch...@cl...> - 2006-02-02 22:04:50
|
On Feb 2, 2006, at 3:10 PM, Julian Mehnle wrote:
> All that doesn't help much as long as the "UNIVERSAL" POD is part
> of the
> official Perl distribution, while the "UNIVERSAL::isa" POD is not.
> This
> makes the "UNIVERSAL" POD look authoritative, not the other POD.
>
> Besides, the "UNIVERSAL::isa" POD doesn't explain the issue very
> well, see
> above. And why is it titled "Hack around people calling
> UNIVERSAL::can()
> as a function" if it is supposed to actually _improve_ code, which
> is not
> normally the case with _hacks_?
Look again at Perl 5.8.8 or 5.9.3. The UNIVERSAL.pm POD was updated
(by chromatic, I think, a month or two ago) to reflect the current
accepted best practice. To quote:
"UNIVERSAL" provides the following methods:
"$obj->isa( TYPE )"
"CLASS->isa( TYPE )"
"eval { VAL->isa( TYPE ) }"
[... snip ...]
You may request the import of all three functions ("isa",
"can", and
"VERSION"), however it is usually harmful to do so. Please
don't do
this in new code.
Chris
--
Chris Dolan, Software Developer, Clotho Advanced Media Inc.
608-294-7900, fax 294-7025, 1435 E Main St, Madison WI 53703
vCard: http://www.chrisdolan.net/ChrisDolan.vcf
Clotho Advanced Media, Inc. - Creators of MediaLandscape Software
(http://www.media-landscape.com/) and partners in the revolutionary
Croquet project (http://www.opencroquet.org/)
|