Thread: [Module::Build] PROPOSAL: M::B should install HTML documentation
Status: Beta
Brought to you by:
kwilliams
|
From: <kev...@ub...> - 2003-10-15 11:13:48
|
All The following comments were collected from a previous thread: SP> As an aside, I always thought it would be a bit more useful to=20 SP> generate HTML manpages on Win32 & friends, considering they don't = have=20 SP> a 'man' tool by default. =20 SP> For example, ACTION_docs could be split up into ACTION_manpages,=20 SP> ACTION_html_docs: =20 SP> ./Build html_docs SP> ./Build manpages =20 SP> And there could be config parameters set for each (with sensible=20 SP> defaults for each OS) that ACTION_docs checked before trying to do=20 SP> anything. =20 SP> What do people think: a useful idea, or feature creep? YS-T> I'd think whether to install HTML would be a separate option, = unrelated YS-T> to man pages. =20 SP> Yeah - that discussion really belongs in a separate thread. So, here is the new thread :-) I would love to see M::B support building HTML documentation. I don't = think this is at all specific to platforms that don't have man. I work on = Unix, but try to provide all documentation in both man and HTML form. Currently, = I achieve this by using M::B to generate the man pages, then installhtml = [1] to generate HTML documentation. However, there are two problems with = this: a) installhtml appears to be a bit of an obscure part of the Perl = distribution. For example, IIRC, it is not installed into the bin directory by = default; you have to copy it yourself. It also does not support all the features of = pod2html. In particular, I want to use the --cachedir option (not documented in = pod2html but documented in the Pod::Html page). b) It is not as elegant as if the functionality were integrated into = M::B. My favoured interface would be that ./Build generates man pages as it = does now. A separate action - ./Build htmldocs - would invoke pod2html (or maybe = Pod::Html directly, not sure) to generate the HTML docs. There would need to be a way to pass options through to pod2html. Maybe something like the = following: ./Build htmldocs -htmldir=3D/foo --htmlroot=3D/bar --cachedir=3Dname = [etc] 1. http://search.cpan.org/dist/perl/installhtml Cheers Kevin +ANYTHING+BELOW+WAS+ADDED+AFTER+I+HIT+SEND+ Visit our website at http://www.ubs.com This message contains confidential information and is intended only for the individual named. If you are not the named addressee you should not disseminate, distribute or copy this e-mail. Please notify the sender immediately by e-mail if you have received this e-mail by mistake and delete this e-mail from your system. E-mail transmission cannot be guaranteed to be secure or error-free as information could be intercepted, corrupted, lost, destroyed, arrive late or incomplete, or contain viruses. The sender therefore does not accept liability for any errors or omissions in the contents of this message which arise as a result of e-mail transmission. If verification is required please request a hard-copy version. This message is provided for informational purposes and should not be construed as a solicitation or offer to buy or sell any securities or related financial instruments. |
|
From: Chris D. <ch...@cl...> - 2003-10-15 15:39:18
|
On Wednesday, October 15, 2003, at 06:12 AM, <kev...@ub...>
wrote:
> I would love to see M::B support building HTML documentation.
I have built a module to do exactly that. It is not yet published to
CPAN, but here's the documentation. The main work left to do is to
tweak some of the defaults and to get a Pod::Html regression bug fixed
(doesn't handle nested =item tags right, grr...)
Chris
NAME
Module::Build::Html - HTML documentation for packages
DESCRIPTION
This package adds automatic generation of HTML documentation to
Module::Build.
SYNOPSIS
Use almost exactly like Module::Build. That is, for end users:
cd My-Package
perl Build.PL installhtml=/my/dir/for/html
Build
Build test
Build install
See the configuration section below for details on how to use the
optional arguments to "perl Build.PL".
For developers, create a file called Build.PL that looks something
like:
use Module::Build;
my $builder = "Module::Build";
eval 'use Module::Build::Html; $builder .= "::Html"; ';
$builder -> new(
module_name => 'My::Package',
license => 'gpl',
requires => {
...
},
)->create_build_script;
CONFIGURATION
We've added a few custom configuration parameters that you can use.
Typically, these are useful to the end user, not the developer. As
such,
use them like this:
perl Build.pl key=value
instead of putting them into the Build.PL directly.
css=URL
Defaults to <http://www.clotho.com/css/perldoc.css>. Decent
alternatives include <http://search.cpan.org/s/style.css> and
<http://perldoc.com/css/perldoc.css>. You should feel to
download
and edit one of these for your own purposes. "file://" URLs work
just great for local CSS files.
installhtml=DIR
Defaults to "undef", which means that HTML files are created,
but
not installed by default. Decent alternatives include, for
example,
"/usr/share/perlhtml" and "$HOME/Documents/perl".
htmlext=EXTENSION
Defaults to "html". Decent alternatives include, for example,
"shtml", "HTM" and "php".
OVERRIDDEN METHODS
_construct KEY => VALUE, KEY => VALUE, ...
This method is called internally by new(). We apply our custom
configuration value here (unless the user has already overridden
them in "perl Build.PL").
_set_install_paths
Add the "installhtml" directory to the list of installation
directories, if needed.
ACTION_docs
Add HTML output to the documentation creation step.
NEW METHODS
htmlify_all_pods
This closely parallels the manify_bin_pods() and
manify_lib_pods()
methods from Module::Build::Base. All files which might contain
POD
are examined and HTML versions of the POD are output to the
"blib/htmldoc" directory. Pod::Html is loaded to perform the
conversion.
AUTHOR
Clotho Advanced Media Inc., *cp...@cl...*
--
Chris Dolan, Software Developer, Clotho Advanced Media Inc.
ch...@cl..., 294-7900, 211 S Paterson, Madison WI 53703
|
|
From: Michael G S. <sc...@po...> - 2003-10-15 22:24:57
|
On Wed, Oct 15, 2003 at 10:39:12AM -0500, Chris Dolan wrote:
> On Wednesday, October 15, 2003, at 06:12 AM, <kev...@ub...>
> wrote:
> >I would love to see M::B support building HTML documentation.
>
>
> I have built a module to do exactly that. It is not yet published to
> CPAN, but here's the documentation. The main work left to do is to
> tweak some of the defaults and to get a Pod::Html regression bug fixed
> (doesn't handle nested =item tags right, grr...)
A few things to note:
5.8.1 finally included Config options for HTML installation directories.
So look for $Config{install*html*dir} (installhtml1dir, installhtml3dir,
installsitehtml1dir, etc...) and if they're set, Module::Build should
install HTML docs by default.
I would avoid passing Pod::Html specific flags into Module::Build. Have
a plan ready to map them in case Pod::Html is replaced. The names of
the Pod::Html arguments are fairly generic, so its sort of moot. Just keep
your upgrade path open in case one of the future POD->HTML modules on CPAN
matures.
--
Michael G Schwern sc...@po... http://www.pobox.com/~schwern/
Kindly do not attempt to cloud the issue with facts.
|
|
From: Randy W. S. <Ra...@Th...> - 2003-10-15 23:07:10
|
On 10/15/2003 6:23 PM, Michael G Schwern wrote:
> On Wed, Oct 15, 2003 at 10:39:12AM -0500, Chris Dolan wrote:
>
>>On Wednesday, October 15, 2003, at 06:12 AM, <kev...@ub...>
>>wrote:
>>
>>>I would love to see M::B support building HTML documentation.
>>
>>
>>I have built a module to do exactly that. It is not yet published to
>>CPAN, but here's the documentation. The main work left to do is to
>>tweak some of the defaults and to get a Pod::Html regression bug fixed
>>(doesn't handle nested =item tags right, grr...)
>
>
> A few things to note:
>
> 5.8.1 finally included Config options for HTML installation directories.
> So look for $Config{install*html*dir} (installhtml1dir, installhtml3dir,
> installsitehtml1dir, etc...) and if they're set, Module::Build should
> install HTML docs by default.
IIRC, some of those have been around longer but where unused; I don't
think anyone ever agreed on where html docs should be installed.
> I would avoid passing Pod::Html specific flags into Module::Build. Have
> a plan ready to map them in case Pod::Html is replaced. The names of
> the Pod::Html arguments are fairly generic, so its sort of moot. Just keep
> your upgrade path open in case one of the future POD->HTML modules on CPAN
> matures.
ActiveState distributions have their own means of generating html from
pod (the ActivePerl::DocTools module), if that module is present you
probably should use it or atleast ask the user.
There is another issue here in that the list of actions is growing quite
large, and M::B is (IMHO) turning into a large monolithic module. Would
it be worth the effort to re-architect actions, so that they are
plug-ins (M::B::ACTION::html, M::B::ACTION::dist). This would keep M::B
from becoming monolithic and would allow others to develop plug-ins to
expand functionality without requiring invasive code changes.
This is only half thought-out, so I'm not sure if it's practical. For
example, I don't know how easy it would be to come up with an interface
so that actions could get info from M::B without digging into the
internals.
<thinking out loud>
Each module would contain one or more actions. M::B would search for
plugin actions at startup, querying each module for a list of supported
actions. Each action knows what parameters it accepts and can display
its own help info. There could be a helper class for generating actions
and linking them by dependencies, i.e.
my $action = new M::B::ACTION::Builder(
name => 'My_lib',
code => sub {},
...
);
my $top = new M::B::ACTION::Builtin( name => 'all' );
$top->add_dependency($action);
</thinking out loud>
Just a thought,
Randy.
--
A little learning is a dang'rous thing;
Drink deep, or taste not the Pierian spring;
There shallow draughts intoxicate the brain;
And drinking largely sobers us again.
- Alexander Pope
|
|
From: Michael G S. <sc...@po...> - 2003-10-16 01:54:44
|
On Wed, Oct 15, 2003 at 07:06:49PM -0400, Randy W. Sims wrote:
> >5.8.1 finally included Config options for HTML installation directories.
> >So look for $Config{install*html*dir} (installhtml1dir, installhtml3dir,
> >installsitehtml1dir, etc...) and if they're set, Module::Build should
> >install HTML docs by default.
>
> IIRC, some of those have been around longer but where unused; I don't
> think anyone ever agreed on where html docs should be installed.
Configure finally asks you where you want to install HTML documentation.
The default is not to install.
--
Michael G Schwern sc...@po... http://www.pobox.com/~schwern/
Abandon failing tactics.
|
|
From: Steve P. <sp...@qu...> - 2003-10-16 09:40:41
|
On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote:
> On 10/15/2003 6:23 PM, Michael G Schwern wrote:
>> On Wed, Oct 15, 2003 at 10:39:12AM -0500, Chris Dolan wrote:
>>> On Wednesday, October 15, 2003, at 06:12 AM, <kev...@ub...>
>>> wrote:
>>>
>>>> I would love to see M::B support building HTML documentation.
>>>
>>>
>>> I have built a module to do exactly that. It is not yet published
>>> to CPAN, but here's the documentation. The main work left to do is
>>> to tweak some of the defaults and to get a Pod::Html regression bug
>>> fixed (doesn't handle nested =item tags right, grr...)
>> A few things to note:
>> 5.8.1 finally included Config options for HTML installation
>> directories.
>> So look for $Config{install*html*dir} (installhtml1dir,
>> installhtml3dir,
>> installsitehtml1dir, etc...) and if they're set, Module::Build should
>> install HTML docs by default.
>
> IIRC, some of those have been around longer but where unused; I don't
> think anyone ever agreed on where html docs should be installed.
>
>> I would avoid passing Pod::Html specific flags into Module::Build.
>> Have
>> a plan ready to map them in case Pod::Html is replaced. The names of
>> the Pod::Html arguments are fairly generic, so its sort of moot.
>> Just keep your upgrade path open in case one of the future POD->HTML
>> modules on CPAN matures.
>
> ActiveState distributions have their own means of generating html from
> pod (the ActivePerl::DocTools module), if that module is present you
> probably should use it or atleast ask the user.
I'd argue that it's up to ActiveState to override M::B's behaviour in
this case. But I've no knowledge of how ActivePerl::DocTools works (I
can't find it on CPAN), so I don't know how feasible that is.
> There is another issue here in that the list of actions is growing
> quite large, and M::B is (IMHO) turning into a large monolithic > module.
This too belongs in another thread :).
In context of HTML docs, I think it makes sense to support it
(especially if it is supported in perl-5.8.1), and I don't think it
would be the straw that broke the camel's back.
-Steve
|
|
From: Steve P. <sp...@qu...> - 2003-10-16 10:57:51
|
On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote:
> There is another issue here in that the list of actions is growing
> quite large, and M::B is (IMHO) turning into a large monolithic
> module. Would it be worth the effort to re-architect actions, so that
> they are plug-ins (M::B::ACTION::html, M::B::ACTION::dist). This would
> keep M::B from becoming monolithic and would allow others to develop
> plug-ins to expand functionality without requiring invasive code
> changes.
Yes, M::B: is becoming quite monolithic. So was MakeMaker. The main
difference I find between the two is that M::B is much easier to read,
understand and extend.
Splitting out the actions is not a bad idea - I bounced a similar one
around with some friends in the past:
package My::Action;
use base qw( Module::Build::Action );
use constant depends_on => qw( foo, bar );
sub builder { } # the parent M::B obj
sub check_dependencies {
...
}
sub execute {
...
}
There are a few problems with these ideas: how do you write platform
specific code? One answer is to sub-class each action, or write a
method for each platform in the action's class. That could work, but
it could also get a bit hairy.
Then, how do you access commonly used methods? A solution might be to
bung all these methods in a common place that everybody inherits from,
say M::B::Common:
M::B::Common
|
+- M::B::Base
|
+- M::B::Action
|
+- M::B::Action::Foo
And, how do you subclass actions? If I want to write my own 'install'
action, how do I do it? At the moment, I sub-class M::B and do it
there. Sub-classing each action is easy, but how do you get M::B to
use them? One way is to say M::B::Base must have stub methods for each
action so that they can be overridden.
I think they all stem from the fact that M::B was not written with this
design in mind, so it would take a lot of effort to refactor things.
It certainly would involve a *lot* of changes, and little (if any)
backwards compatibility. There is potentially a huge gain as far as
simplicity is concerned, but it may be more work than it's worth.
That said...
> This is only half thought-out, so I'm not sure if it's practical. For
> example, I don't know how easy it would be to come up with an
> interface so that actions could get info from M::B without digging
> into the internals.
Making them plugins is a good idea, but it's even more complicated.
> <thinking out loud>
>
> Each module would contain one or more actions.
I'd limit it to one a package. That way you can get the action name
from the package name itself:
'build' => M::B::Action::Build
> M::B would search for plugin actions at startup, querying each module
> for a list of supported actions.
So M::B builds up a list of actions and their classes? And I assume it
would search particular sub-classes, starting with
Module::Build::Action? Maybe that's how you could do user level
sub-classing: push on your own 'search' package:
@M::B::SearchPackages = qw( My::Action Module::Build::Action );
Then it would find your actions first. If you have 1 action per
package, then M::B already knows what it does. So eventually you build
a map:
$build->{ActionClassMap} = {
Build => 'My::Action::Build'
Install => 'M::B::Action::Install'
};
Then to dispatch, you do something like:
sub dispatch {
my $self = shift;
my $action = shift;
my $aclass = $self->{ActionClassMap}->{$action} || die;
$aclass->new->builder( $self )->check_dependencies->execute;
}
> Each action knows what parameters it accepts and can display its own
> help info. There could be a helper class for generating actions and
> linking them by dependencies, i.e.
>
> my $action = new M::B::ACTION::Builder(
> name => 'My_lib',
> code => sub {},
> ...
> );
Dunno if that's needed - M::B already does it.
> my $top = new M::B::ACTION::Builtin( name => 'all' );
> $top->add_dependency($action);
I'd go with 'M::B::Action::All', it's more consistent.
Food for thought...
-Steve
|
|
From: Ken W. <ke...@ma...> - 2003-10-16 13:29:23
|
On Thursday, October 16, 2003, at 05:50 AM, Steve Purkis wrote: > I think they all stem from the fact that M::B was not written with > this design in mind, so it would take a lot of effort to refactor > things. It certainly would involve a *lot* of changes, and little (if > any) backwards compatibility. There is potentially a huge gain as far > as simplicity is concerned, but it may be more work than it's worth. I think we could do it without too much difficulty, actually. I'll ponder it... This is something I've been wanting to do in the back of my mind too. It sure would be nice to have local-only actions, easily redistributable actions, etc. -Ken |
|
From: Randy W. S. <Ra...@Th...> - 2003-10-17 02:58:40
|
Thanks for the discussion; It has caused me think this through a little
more thoroughly. But the thoughts below should still be considered
idealizations and may differ from the possible or practical. ;)
On 10/16/2003 6:50 AM, Steve Purkis wrote:
> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote:
>
>> There is another issue here in that the list of actions is growing
>> quite large, and M::B is (IMHO) turning into a large monolithic
>> module. Would it be worth the effort to re-architect actions, so that
>> they are plug-ins (M::B::ACTION::html, M::B::ACTION::dist). This would
>> keep M::B from becoming monolithic and would allow others to develop
>> plug-ins to expand functionality without requiring invasive code changes.
>
>
> Yes, M::B: is becoming quite monolithic. So was MakeMaker. The main
> difference I find between the two is that M::B is much easier to read,
> understand and extend.
I didn't realize until now that my original choice of words may have
been misconstrued as an insulting remark--That was not my intent. I
meant monolithic in the sense that most of the code is in a single
file/class (M::B::Base) that it is growing quite large, and it is not
likely to get smaller. I didn't intend it as an insult or as any kind of
judgemental statement, just as an observation.
I don't know that I would describe MakeMaker as monolithic. Michael
Schwern has done a great job of cleaning it up. It's main problem is
that it is limited to the underlying tool--make, and to it's different
versions, implementations, and platforms.
> Splitting out the actions is not a bad idea - I bounced a similar one
> around with some friends in the past:
>
> package My::Action;
>
> use base qw( Module::Build::Action );
> use constant depends_on => qw( foo, bar );
>
> sub builder { } # the parent M::B obj
>
> sub check_dependencies {
> ...
> }
>
> sub execute {
> ...
> }
>
> There are a few problems with these ideas: how do you write platform
> specific code? One answer is to sub-class each action, or write a
> method for each platform in the action's class. That could work, but it
> could also get a bit hairy.
The idea(l) is that M::B provide as much as possible an API for the
action plugins that is platform independent. If there are still OS
differences and they are small, they can be put inline. Otherwise, you
would create subclasses that handled the differences and use the 'base'
class as a proxy, forwarding the calls to the appropriate 'subclass'.
i.e. if you write an action, say for reporting build status to
cpan-testers called M::B::Action::Reports. If there are differnces
between *nix systems & Windows, you'd create classes
M::B::Action::Reports::Unix & M::B::Action::Reports::Windows. Now, in
M::B::Action::Reports you'd write stub functions that determinse the
appropriate subclass to call based on OS and forward the call to the
real function in the determined subclass.
> Then, how do you access commonly used methods? A solution might be to
> bung all these methods in a common place that everybody inherits from,
> say M::B::Common:
>
> M::B::Common
> |
> +- M::B::Base
> |
> +- M::B::Action
> |
> +- M::B::Action::Foo
Again, the common methods would become part of M::B's API for actions.
(Nit: It's a bad idea to subclass just to share code.)
> And, how do you subclass actions? If I want to write my own 'install'
> action, how do I do it? At the moment, I sub-class M::B and do it
> there. Sub-classing each action is easy, but how do you get M::B to use
> them? One way is to say M::B::Base must have stub methods for each
> action so that they can be overridden.
As an Build.PL author, you would do one of two things. 1) You'd use a
helper class, something like
my $action = new M::B::Action::Builder(
name => 'install',
code => sub {...},
help => 'A help message to display for users.'
);
M::B::Action::register( $action );
or 2) You'd create an inline class and register it:
package MY::Action::install;
use Module::Build::Action::Install;
use vars qw(@ISA);
@ISA = qw(Module::Build::Action::Install);
sub new { return Module::Build::Action::register( bless \my $arse ) }
sub ACTION_install { ... }
package main
new MY::Action::install;
...
> I think they all stem from the fact that M::B was not written with this
> design in mind, so it would take a lot of effort to refactor things. It
> certainly would involve a *lot* of changes, and little (if any)
> backwards compatibility. There is potentially a huge gain as far as
> simplicity is concerned, but it may be more work than it's worth.
Yeah, I have no doubt it would be difficult to change, but M::B is not
going to get smaller. So, any refactoring is best done sooner than later.
> That said...
>
>> This is only half thought-out, so I'm not sure if it's practical. For
>> example, I don't know how easy it would be to come up with an
>> interface so that actions could get info from M::B without digging
>> into the internals.
>
>
> Making them plugins is a good idea, but it's even more complicated.
>
>
>> <thinking out loud>
>>
>> Each module would contain one or more actions.
>
>
> I'd limit it to one a package. That way you can get the action name
> from the package name itself:
>
> 'build' => M::B::Action::Build
It would be more convenient to allow multiple related actions to share a
file so that they can share common functions/data. Also, it's best if
they do not share the name because the most appropriate name for an
action might be a perl keyword. The module should have a descriptive
name appropriate for the actions it contains. The actions
(packages/classes) themselves should probably follow the current
standard of prefacing them with 'ACTION_' to avoid conflicts.
>> M::B would search for plugin actions at startup, querying each module
>> for a list of supported actions.
>
>
> So M::B builds up a list of actions and their classes? And I assume it
> would search particular sub-classes, starting with
> Module::Build::Action? Maybe that's how you could do user level
> sub-classing: push on your own 'search' package:
>
> @M::B::SearchPackages = qw( My::Action Module::Build::Action );
This is where I still have uncertainties. One way that seems kind of
hackish to me is to scan for '*.pm' under M::B::Action. Then try to
execute a register function for each module found. If an author provides
an action that is intended to subclass/override another action, then the
register function for that action would have to report that in some way.
I think this kind of usage would be rare in practice. ???
> Then it would find your actions first. If you have 1 action per
> package, then M::B already knows what it does. So eventually you build
> a map:
>
> $build->{ActionClassMap} = {
> Build => 'My::Action::Build'
> Install => 'M::B::Action::Install'
> };
>
> Then to dispatch, you do something like:
>
> sub dispatch {
> my $self = shift;
> my $action = shift;
> my $aclass = $self->{ActionClassMap}->{$action} || die;
> $aclass->new->builder( $self )->check_dependencies->execute;
> }
>
>
>> Each action knows what parameters it accepts and can display its own
>> help info. There could be a helper class for generating actions and
>> linking them by dependencies, i.e.
>>
>> my $action = new M::B::ACTION::Builder(
>> name => 'My_lib',
>> code => sub {},
>> ...
>> );
>
>
> Dunno if that's needed - M::B already does it.
Do you mean the subclass() method? In the way I'm describing, you would
subclass on an action by action basis rather than subclass M::B; in
fact, you would probably rarely if ever need to subclass M::B itself.
>> my $top = new M::B::ACTION::Builtin( name => 'all' );
>> $top->add_dependency($action);
>
>
> I'd go with 'M::B::Action::All', it's more consistent.
Actually, right now I'm thinking more along the lines of
my $top = new M::B::Action::find_action( name => 'all' );
$top->add_dependency($action);
to get a reference to an existing action as it seems more flexible.
> Food for thought...
>
> -Steve
>
Randy.
|
|
From: Ken W. <ke...@ma...> - 2003-10-17 04:14:20
|
On Thursday, October 16, 2003, at 09:57 PM, Randy W. Sims wrote: > > On 10/16/2003 6:50 AM, Steve Purkis wrote: >> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote: >>> There is another issue here in that the list of actions is growing >>> quite large, and M::B is (IMHO) turning into a large monolithic >>> module. Would it be worth the effort to re-architect actions, so >>> that they are plug-ins (M::B::ACTION::html, M::B::ACTION::dist). >>> This would keep M::B from becoming monolithic and would allow others >>> to develop plug-ins to expand functionality without requiring >>> invasive code changes. >> Yes, M::B: is becoming quite monolithic. So was MakeMaker. The main >> difference I find between the two is that M::B is much easier to >> read, understand and extend. > > I didn't realize until now that my original choice of words may have > been misconstrued as an insulting remark--That was not my intent. I > meant monolithic in the sense that most of the code is in a single > file/class (M::B::Base) that it is growing quite large, and it is not > likely to get smaller. I didn't intend it as an insult or as any kind > of judgemental statement, just as an observation. Oh, I took no offense, and I think I would actually make the same criticism myself. The monolithic nature of M::B is something I've been thinking about a lot (well, maybe just "some") lately. A basic design stress in designing M::B was the following conflict: A) it should be easy to subclass, meaning that all methods should be part of a single class hierarchy B) it should be scalable, meaning that multiple classes should interrelate to accomplish tasks, and the user should be able to swap out different parts of the system with their own parts. In the end I chose A). A system like Mason uses B). I think the trick now is to get the benefits of A) while incorporating some of the benefits of B), and I think a plug-in system could do that nicely. > > I don't know that I would describe MakeMaker as monolithic. Michael > Schwern has done a great job of cleaning it up. It's main problem is > that it is limited to the underlying tool--make, and to it's different > versions, implementations, and platforms. Yeah, MakeMaker is sort of multi-monolithic. Or Stonehengian. ;-) -Ken |
|
From: Steve P. <sp...@qu...> - 2003-10-17 15:39:59
|
On Friday, October 17, 2003, at 03:57 am, Randy W. Sims wrote:
>
> Thanks for the discussion; It has caused me think this through a=20
> little more thoroughly.
Likewise - I'm glad I'm not the only one thinking about this, and it's=20=
good to talk it over.
> But the thoughts below should still be considered idealizations and=20=
> may differ from the possible or practical. ;)
>
> On 10/16/2003 6:50 AM, Steve Purkis wrote:
>> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote:
>>> There is another issue here in that the list of actions is growing=20=
>>> quite large, and M::B is (IMHO) turning into a large monolithic=20
>>> module. Would it be worth the effort to re-architect actions, so=20
>>> that they are plug-ins (M::B::ACTION::html, M::B::ACTION::dist).=20
>>> This would keep M::B from becoming monolithic and would allow others=20=
>>> to develop plug-ins to expand functionality without requiring=20
>>> invasive code changes.
>> Yes, M::B: is becoming quite monolithic. So was MakeMaker. The main=20=
>> difference I find between the two is that M::B is much easier to=20
>> read, understand and extend.
>
> I didn't realize until now that my original choice of words may have=20=
> been misconstrued as an insulting remark--That was not my intent. I=20
> meant monolithic in the sense that most of the code is in a single=20
> file/class (M::B::Base) that it is growing quite large, and it is not=20=
> likely to get smaller. I didn't intend it as an insult or as any kind=20=
> of judgemental statement, just as an observation.
I don't think anyone interpreted it in an insulting way :). Also, the=20=
above was more a blaz=E9 remark than anything else.
>> Splitting out the actions is not a bad idea - I bounced a similar one=20=
>> around with some friends in the past:
>> package My::Action;
>> use base qw( Module::Build::Action );
>> use constant depends_on =3D> qw( foo, bar );
>> sub builder { } # the parent M::B obj
>> sub check_dependencies {
>> ...
>> }
>> sub execute {
>> ...
>> }
>> There are a few problems with these ideas: how do you write platform=20=
>> specific code? One answer is to sub-class each action, or write a=20
>> method for each platform in the action's class. That could work, but=20=
>> it could also get a bit hairy.
>
> The idea(l) is that M::B provide as much as possible an API for the=20
> action plugins that is platform independent. If there are still OS=20
> differences and they are small, they can be put inline. Otherwise, you=20=
> would create subclasses that handled the differences and use the=20
> 'base' class as a proxy, forwarding the calls to the appropriate=20
> 'subclass'.
>
> i.e. if you write an action, say for reporting build status to=20
> cpan-testers called M::B::Action::Reports. If there are differnces=20
> between *nix systems & Windows, you'd create classes=20
> M::B::Action::Reports::Unix & M::B::Action::Reports::Windows. Now, in=20=
> M::B::Action::Reports you'd write stub functions that determinse the=20=
> appropriate subclass to call based on OS and forward the call to the=20=
> real function in the determined subclass.
Yeah, that would do the trick.
>> Then, how do you access commonly used methods? A solution might be=20=
>> to bung all these methods in a common place that everybody inherits=20=
>> from, say M::B::Common:
>> M::B::Common
>> |
>> +- M::B::Base
>> |
>> +- M::B::Action
>> |
>> +- M::B::Action::Foo
>
> Again, the common methods would become part of M::B's API for actions.=20=
> (Nit: It's a bad idea to subclass just to share code.)
I disagree - sharing code is one of the best reasons to subclass. But=20=
I don't want to be pedantic here, because I think having a API for M::B=20=
actions is an excellent idea, and I imagine it would be a bit of mix &=20=
match as to what makes more sense in the sub-class, and what makes more=20=
sense in the API.
>> And, how do you subclass actions? If I want to write my own=20
>> 'install' action, how do I do it? At the moment, I sub-class M::B=20
>> and do it there. Sub-classing each action is easy, but how do you=20
>> get M::B to use them? One way is to say M::B::Base must have stub=20
>> methods for each action so that they can be overridden.
>
> As an Build.PL author, you would do one of two things.
>
> 1) You'd use a helper class, something like
>
> my $action =3D new M::B::Action::Builder(
> name =3D> 'install',
> code =3D> sub {...},
> help =3D> 'A help message to display for users.'
> );
>
> M::B::Action::register( $action );
Cool idea - fits in well with Module::Build->subclass(). The=20
registration could be done automatically by the builder too.
As an aside, I'd move the Builder somewhere else and reserve the=20
M::B::Action:: namespace for actions only.
> or 2) You'd create an inline class and register it:
>
> package MY::Action::install;
> use Module::Build::Action::Install;
> use vars qw(@ISA);
> @ISA =3D qw(Module::Build::Action::Install);
> sub new { return Module::Build::Action::register( bless \my $arse ) }
> sub ACTION_install { ... }
>
> package main
> new MY::Action::install;
> ...
If you're gonna use a register() method, I'd say do it at a class=20
level, and keep with Perl's naming convention (lowercase reserved for=20
pragmas):
package My::Action::Install
use base qw( Module::Build::Action::Install );
Module::Build::Action->register( install =3D> __PACKAGE__ );
sub dispatch { ... } # or execute() or whatever()
(This is still assuming you have one action per package - I'll get to=20
that below).
>> I think they all stem from the fact that M::B was not written with=20
>> this design in mind, so it would take a lot of effort to refactor=20
>> things. It certainly would involve a *lot* of changes, and little=20
>> (if any) backwards compatibility. There is potentially a huge gain=20=
>> as far as simplicity is concerned, but it may be more work than it's=20=
>> worth.
>
> Yeah, I have no doubt it would be difficult to change, but M::B is not=20=
> going to get smaller. So, any refactoring is best done sooner than=20
> later.
Good point.
>> That said...
>>> This is only half thought-out, so I'm not sure if it's practical.=20
>>> For example, I don't know how easy it would be to come up with an=20
>>> interface so that actions could get info from M::B without digging=20=
>>> into the internals.
>> Making them plugins is a good idea, but it's even more complicated.
>>> <thinking out loud>
>>>
>>> Each module would contain one or more actions.
>> I'd limit it to one a package. That way you can get the action name=20=
>> from the package name itself:
>> 'build' =3D> M::B::Action::Build
>
> It would be more convenient to allow multiple related actions to share=20=
> a file so that they can share common functions/data. Also, it's best=20=
> if they do not share the name because the most appropriate name for an=20=
> action might be a perl keyword. The module should have a descriptive=20=
> name appropriate for the actions it contains. The actions=20
> (packages/classes) themselves should probably follow the current=20
> standard of prefacing them with 'ACTION_' to avoid conflicts.
I see where you're coming from, but I still disagree. Here's why:
1. Simplicity.
This is the key, really. A class that does one thing and does it well=20=
is easier to write, maintain, understand and sub-class. There's also=20
less to go wrong, and it will make dispatching the actions a lot=20
easier, seeing as each action would be dispatched in the same way:
$action->dispatch(); # thank you Pipeline.pm :)
2. Common code can be inherited or called out.
We talked about inheritance above; with dependencies between actions,=20
common code can be easily called out. The M::B API for actions would=20
also fit in here.
3. Highly configurable.
If each action is implemented as a small unit of code, you could easily=20=
change the behaviour of the system by replacing one of the units.
I don't think having an action that has the same name as a perl keyword=20=
(say 'if') would be a problem here.
>>> M::B would search for plugin actions at startup, querying each=20
>>> module for a list of supported actions.
>> So M::B builds up a list of actions and their classes? And I assume=20=
>> it would search particular sub-classes, starting with=20
>> Module::Build::Action? Maybe that's how you could do user level=20
>> sub-classing: push on your own 'search' package:
>> @M::B::SearchPackages =3D qw( My::Action Module::Build::Action );
>
> This is where I still have uncertainties. One way that seems kind of=20=
> hackish to me is to scan for '*.pm' under M::B::Action. Then try to=20
> execute a register function for each module found. If an author=20
> provides an action that is intended to subclass/override another=20
> action, then the register function for that action would have to=20
> report that in some way. I think this kind of usage would be rare in=20=
> practice. ???
I've done some work along these lines in the past - right off the bat=20
I'll tell ya that looking for *.pm doesn't work as well as you might=20
like :-/.
Here are a couple of algorithms I've used in the past (I'm sure there's=20=
more ;)...
1. A type map & a register method.
The idea is you have an hash like the 'ActionClassMap' I mentioned=20
before (pls forgive the studley caps :). That hash is populated=20
internally by the module author:
package Module::Build::Action;
our %ActionClassMap =3D
(
Build =3D> 'My::Action::Build',
...
);
Externally, that hash is populated by a 'register' method like the one=20=
you had above:
package My::Module::Action::Build;
use base qw( Modle::Build::Action::Build );
Module::Build::Action->register( build =3D> __PACKAGE__ );
# or alternatively:
__PACKAGE__->register( 'build' );
2. Search packages.
This option is potentially less work, but not as flexible in naming=20
convention - the idea is you have a rule for naming action classes=20
under a namespace, and an ordered list of packages to search. For=20
example:
# assuming a 'ucfirst' rule:
$class =3D ucfirst( $action ); # 'build' =3D> 'Build'
@search_pkgs =3D qw( Module::Build::Action );
foreach my $pkg ( @search_pkgs ) {
$pkg .=3D "::$class";
return $pkg if $pkg->can( 'new' );
eval "require $pkg";
return $pkg unless $@;
}
die "couldn't find action $action\n";
>>> Each action knows what parameters it accepts and can display its=20
>>> own help info. There could be a helper class for generating actions=20=
>>> and linking them by dependencies, i.e.
>>>
>>> my $action =3D new M::B::ACTION::Builder(
>>> name =3D> 'My_lib',
>>> code =3D> sub {},
>>> ...
>>> );
>> Dunno if that's needed - M::B already does it.
>
> Do you mean the subclass() method? In the way I'm describing, you=20
> would subclass on an action by action basis rather than subclass M::B;=20=
> in fact, you would probably rarely if ever need to subclass M::B=20
> itself.
Yeah, I think I missed the point last time :). Gotcha now.
>>> my $top =3D new M::B::ACTION::Builtin( name =3D> 'all' );
>>> $top->add_dependency($action);
>> I'd go with 'M::B::Action::All', it's more consistent.
>
> Actually, right now I'm thinking more along the lines of
>
> my $top =3D new M::B::Action::find_action( name =3D> 'all' );
> $top->add_dependency($action);
>
> to get a reference to an existing action as it seems more flexible.
Mmm.. so you'd like to be able to compile a list of dependencies=20
external to the Actions themselves, eh? Well, it's not what I was=20
thinking but it could work.
Still, after a bit of thought I'd say keep it simple and sub-class if=20
ever you want to change the dependency tree. Which is not as flexible=20=
when you're writing Build.PL's, but it does mean less work for the=20
person implementing/maintaining M::B's action dispatching code.
-Steve
|
|
From: Randy W. S. <Ra...@Th...> - 2003-10-18 09:15:17
|
On 10/17/2003 11:38 AM, Steve Purkis wrote:
> On Friday, October 17, 2003, at 03:57 am, Randy W. Sims wrote:
>
>> On 10/16/2003 6:50 AM, Steve Purkis wrote:
>>
>>> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote:
>>>
>>>> <thinking out loud>
>>>>
>>>> Each module would contain one or more actions.
>>>
>>> I'd limit it to one a package. That way you can get the action name
>>> from the package name itself:
>>> 'build' => M::B::Action::Build
>>
>>
>> It would be more convenient to allow multiple related actions to share
>> a file so that they can share common functions/data. Also, it's best
>> if they do not share the name because the most appropriate name for an
>> action might be a perl keyword. The module should have a descriptive
>> name appropriate for the actions it contains. The actions
>> (packages/classes) themselves should probably follow the current
>> standard of prefacing them with 'ACTION_' to avoid conflicts.
>
>
> I see where you're coming from, but I still disagree. Here's why:
>
> 1. Simplicity.
> This is the key, really. A class that does one thing and does it well
> is easier to write, maintain, understand and sub-class. There's also
> less to go wrong, and it will make dispatching the actions a lot easier,
> seeing as each action would be dispatched in the same way:
>
> $action->dispatch(); # thank you Pipeline.pm :)
>
> 2. Common code can be inherited or called out.
> We talked about inheritance above; with dependencies between actions,
> common code can be easily called out. The M::B API for actions would
> also fit in here.
>
> 3. Highly configurable.
> If each action is implemented as a small unit of code, you could easily
> change the behaviour of the system by replacing one of the units.
>
> I don't think having an action that has the same name as a perl keyword
> (say 'if') would be a problem here.
>
>
Ok, I'm willing to conceed. I think my main objection was aesthetic; it
would mean a lot of subclasses (nearly two dozen for builtins).
>>>> M::B would search for plugin actions at startup, querying each
>>>> module for a list of supported actions.
>>>
>>> So M::B builds up a list of actions and their classes? And I assume
>>> it would search particular sub-classes, starting with
>>> Module::Build::Action? Maybe that's how you could do user level
>>> sub-classing: push on your own 'search' package:
>>> @M::B::SearchPackages = qw( My::Action Module::Build::Action );
>>
>>
>> This is where I still have uncertainties. One way that seems kind of
>> hackish to me is to scan for '*.pm' under M::B::Action. Then try to
>> execute a register function for each module found. If an author
>> provides an action that is intended to subclass/override another
>> action, then the register function for that action would have to
>> report that in some way. I think this kind of usage would be rare in
>> practice. ???
>
>
> I've done some work along these lines in the past - right off the bat
> I'll tell ya that looking for *.pm doesn't work as well as you might
> like :-/.
>
> Here are a couple of algorithms I've used in the past (I'm sure there's
> more ;)...
>
> 1. A type map & a register method.
> The idea is you have an hash like the 'ActionClassMap' I mentioned
> before (pls forgive the studley caps :). That hash is populated
> internally by the module author:
>
> package Module::Build::Action;
> our %ActionClassMap =
> (
> Build => 'My::Action::Build',
> ...
> );
>
> Externally, that hash is populated by a 'register' method like the one
> you had above:
>
> package My::Module::Action::Build;
> use base qw( Modle::Build::Action::Build );
> Module::Build::Action->register( build => __PACKAGE__ );
> # or alternatively:
> __PACKAGE__->register( 'build' );
>
> 2. Search packages.
> This option is potentially less work, but not as flexible in naming
> convention - the idea is you have a rule for naming action classes under
> a namespace, and an ordered list of packages to search. For example:
>
> # assuming a 'ucfirst' rule:
> $class = ucfirst( $action ); # 'build' => 'Build'
> @search_pkgs = qw( Module::Build::Action );
>
> foreach my $pkg ( @search_pkgs ) {
> $pkg .= "::$class";
> return $pkg if $pkg->can( 'new' );
> eval "require $pkg";
> return $pkg unless $@;
> }
>
> die "couldn't find action $action\n";
>
>
>
My ASSumptions here are that M::B would need to construct a list of
actions at startup in order to workout overrides by other action authors
(as opposed to module authors writing Build.PL scripts). I don't think
either of these designs allow for that, but OTOH that restriction could
come from a valid design decision.
>>>> my $top = new M::B::ACTION::Builtin( name => 'all' );
>>>> $top->add_dependency($action);
>>>
>>> I'd go with 'M::B::Action::All', it's more consistent.
>>
>>
>> Actually, right now I'm thinking more along the lines of
>>
>> my $top = new M::B::Action::find_action( name => 'all' );
>> $top->add_dependency($action);
>>
>> to get a reference to an existing action as it seems more flexible.
>
>
> Mmm.. so you'd like to be able to compile a list of dependencies
> external to the Actions themselves, eh? Well, it's not what I was
> thinking but it could work.
>
> Still, after a bit of thought I'd say keep it simple and sub-class if
> ever you want to change the dependency tree. Which is not as flexible
> when you're writing Build.PL's, but it does mean less work for the
> person implementing/maintaining M::B's action dispatching code.
>
> -Steve
M::B itself would manage all dependencies. Build.PL authors, action
authors would be responsible for registering the dependencies with M::B.
M::B would also maintain a dispatch table for all actions and be
responsible for finding and invoking the correct action. So, (third
times a charm) the above would actually be M::B::find_action(). That
gives M::B the flexibiliy to chose the correct action when there may be
overrides involved.
This is about as far as I can think through the design without resorting
to drawing up some diagrams and taking a close look at the existing
code, and Ken may already have something better in mind.
A couple a good design exercises might be 1) to write a sample Build.PL
script and a plugin that uses the as-yet-unimplimented interface, and 2)
draw a call graph that follows execution from the time the user invokes
'./Build action' and decide what methods get called, what class they
belong to, and what they do. (Standard stuff)
In any case, I would suggest that a good first phase might be to look at
all of the existing ACTION_* subs and modify them so that they don't
access M::B's innards directly and simultaneously build up an API.
Then it might be possible to play around with different plugin designs.
I think the key to this phase will be two routines. 1) something along
the lines of M::B::scan_plugins() that would magically locate plugins,
passing each to... 2) a M::B::register_plugins() routine that would
build the master dispatch table (resolving any overrides) and depenency
trees. It would also be responsible for creating virtual stub functions
in M::B::Base that simply forward the calls to the appropriate plugin.
These stubs will allow back-patability, and I think they will allow a
solution to Ken's two design conflicts mentioned in another message.
Regards,
Randy.
|
|
From: Steve P. <sp...@qu...> - 2003-10-19 08:18:52
|
On Saturday, October 18, 2003, at 10:09 am, Randy W. Sims wrote: > On 10/17/2003 11:38 AM, Steve Purkis wrote: >> On Friday, October 17, 2003, at 03:57 am, Randy W. Sims wrote: >>> On 10/16/2003 6:50 AM, Steve Purkis wrote: >>> >>>> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote: >>>> >>>>> M::B would search for plugin actions at startup, querying each >>>>> module for a list of supported actions. >>>> >>>> So M::B builds up a list of actions and their classes? And I >>>> assume it would search particular sub-classes, starting with >>>> Module::Build::Action? Maybe that's how you could do user level >>>> sub-classing: push on your own 'search' package: >>>> @M::B::SearchPackages = qw( My::Action Module::Build::Action ); >>> >>> This is where I still have uncertainties. One way that seems kind of >>> hackish to me is to scan for '*.pm' under M::B::Action. Then try to >>> execute a register function for each module found. If an author >>> provides an action that is intended to subclass/override another >>> action, then the register function for that action would have to >>> report that in some way. I think this kind of usage would be rare in >>> practice. ??? >> I've done some work along these lines in the past - right off the bat >> I'll tell ya that looking for *.pm doesn't work as well as you might >> like :-/. >> Here are a couple of algorithms I've used in the past (I'm sure >> there's more ;)... >> 1. A type map & a register method. >> 2. Search packages. > > My ASSumptions here are that M::B would need to construct a list of > actions at startup in order to workout overrides by other action > authors (as opposed to module authors writing Build.PL scripts). I > don't think either of these designs allow for that, but OTOH that > restriction could come from a valid design decision. They both do allow for overrides by other action authors - in (1) you'd call the register() method, in (2) you'd modify the list of search packages. I've just noticed Module-Pluggable going up to CPAN -- haven't had time to look at it, but might be worth going over it before making any decisions. >>>>> my $top = new M::B::ACTION::Builtin( name => 'all' ); >>>>> $top->add_dependency($action); >>>> >>>> I'd go with 'M::B::Action::All', it's more consistent. >>> >>> >>> Actually, right now I'm thinking more along the lines of >>> >>> my $top = new M::B::Action::find_action( name => 'all' ); >>> $top->add_dependency($action); >>> >>> to get a reference to an existing action as it seems more flexible. >> Mmm.. so you'd like to be able to compile a list of dependencies >> external to the Actions themselves, eh? Well, it's not what I was >> thinking but it could work. > > > > Still, after a bit of thought I'd say keep it simple and sub-class if > > ever you want to change the dependency tree. Which is not as > flexible > > when you're writing Build.PL's, but it does mean less work for the > > person implementing/maintaining M::B's action dispatching code. > > M::B itself would manage all dependencies. Build.PL authors, action > authors would be responsible for registering the dependencies with > M::B. M::B would also maintain a dispatch table for all actions and > be responsible for finding and invoking the correct action. So, (third > times a charm) the above would actually be M::B::find_action(). That > gives M::B the flexibiliy to chose the correct action when there may > be overrides involved. Ahh, ok, that's a different idea than the one I had in mind (let the Actions handle the dependencies). It's really a design decision that's gotta be made by Ken I guess. > This is about as far as I can think through the design without > resorting to drawing up some diagrams and taking a close look at the > existing code, and Ken may already have something better in mind. Yeah, I think we've put forward some good ideas. It's up to Ken to decide which way forward now. Cheers, -Steve |
|
From: Randy W. S. <Ra...@Th...> - 2003-10-19 11:42:57
|
I don't think this made it to the list, so I've forwarded it for Steve. On Saturday, October 18, 2003, at 10:09 am, Randy W. Sims wrote: > On 10/17/2003 11:38 AM, Steve Purkis wrote: > >> On Friday, October 17, 2003, at 03:57 am, Randy W. Sims wrote: >> >>> On 10/16/2003 6:50 AM, Steve Purkis wrote: >>> >>>> On Thursday, October 16, 2003, at 12:06 am, Randy W. Sims wrote: >>>> >>>>> M::B would search for plugin actions at startup, querying each module for a list of supported actions. >>>> >>>> >>>> So M::B builds up a list of actions and their classes? And I assume it would search particular sub-classes, starting with Module::Build::Action? Maybe that's how you could do user level sub-classing: push on your own 'search' package: >>>> @M::B::SearchPackages = qw( My::Action Module::Build::Action ); >>> >>> >>> This is where I still have uncertainties. One way that seems kind of hackish to me is to scan for '*.pm' under M::B::Action. Then try to execute a register function for each module found. If an author provides an action that is intended to subclass/override another action, then the register function for that action would have to report that in some way. I think this kind of usage would be rare in practice. ??? >> >> I've done some work along these lines in the past - right off the bat I'll tell ya that looking for *.pm doesn't work as well as you might like :-/. >> Here are a couple of algorithms I've used in the past (I'm sure there's more ;)... >> 1. A type map & a register method. >> 2. Search packages. > > > My ASSumptions here are that M::B would need to construct a list of actions at startup in order to workout overrides by other action authors (as opposed to module authors writing Build.PL scripts). I don't think either of these designs allow for that, but OTOH that restriction could come from a valid design decision. They both do allow for overrides by other action authors - in (1) you'd call the register() method, in (2) you'd modify the list of search packages. I've just noticed Module-Pluggable going up to CPAN -- haven't had time to look at it, but might be worth going over it before making any decisions. >>>>> my $top = new M::B::ACTION::Builtin( name => 'all' ); >>>>> $top->add_dependency($action); >>>> >>>> >>>> I'd go with 'M::B::Action::All', it's more consistent. >>> >>> >>> >>> Actually, right now I'm thinking more along the lines of >>> >>> my $top = new M::B::Action::find_action( name => 'all' ); >>> $top->add_dependency($action); >>> >>> to get a reference to an existing action as it seems more flexible. >> >> Mmm.. so you'd like to be able to compile a list of dependencies external to the Actions themselves, eh? Well, it's not what I was thinking but it could work. > > > > > Still, after a bit of thought I'd say keep it simple and sub-class if > > ever you want to change the dependency tree. Which is not as flexible > > when you're writing Build.PL's, but it does mean less work for the > > person implementing/maintaining M::B's action dispatching code. > > M::B itself would manage all dependencies. Build.PL authors, action authors would be responsible for registering the dependencies with M::B. M::B would also maintain a dispatch table for all actions and be responsible for finding and invoking the correct action. So, (third times a charm) the above would actually be M::B::find_action(). That gives M::B the flexibiliy to chose the correct action when there may be overrides involved. Ahh, ok, that's a different idea than the one I had in mind (let the Actions handle the dependencies). It's really a design decision that's gotta be made by Ken I guess. > This is about as far as I can think through the design without resorting to drawing up some diagrams and taking a close look at the existing code, and Ken may already have something better in mind. Yeah, I think we've put forward some good ideas. It's up to Ken to decide which way forward now. Cheers, -Steve |