work in progress

git-svn-id: svn://10.0.0.236/trunk@94031 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
ian%hixie.ch 2001-05-05 07:12:56 +00:00
parent 2f7e9262c9
commit 72c3f78013
34 changed files with 4191 additions and 0 deletions

View File

@ -0,0 +1,467 @@
_______________________
CHAPTER 1: INTRODUCTION
| Wherein the author whines about the people who asked for this
| document and denies all responsability for its upkeep.
People said they wanted documentation.
So.
Here it is.
Documentation.
Not that it's going to be very thorough or anything. Since I change
major parts of my codebase on an hourly basis, and I update the
documentation on an annual basis, this is not going to be of much use
to anyone who expects it to be accurate.
I warn you right now: if you complain about inaccuracies, I'll just
give up any pretense of writing documentation.
______________________
CHAPTER 2: THE CONCEPT
| Wherein services are explained to be the Saviour of the human race
| and an attempt is made to remove some of their mystery.
Services are a key concept to the PLIF architecture. They are the PLIF
version of XPCOM components, DOM interfaces, C++ pure virtual classes
or operating system APIs. They abstract out functionality using Perl's
polymorphism support so as to make consumers implementation-agnostic.
66-----------------------+
| But what does it all |
| _mean_, Austin? |
+-----------------------99
Imagine you want to order Pizza. The typical thing to do is call
Domino's Pizza, place your order, and await the food at your front
door. But what if you're on holiday, and Domino's aren't available in
that area? Your call fails, because you are unable to get Domino's to
ship pizza to you from your home town to your hotel on a different
continent, and thus you starve and die.
Clearly this is suboptimal.
Here is an alternative way of ordering pizza. Instead of picking up
the telephone, you pick up the business directory (aka, the yellow
pages). You look up "pizza takeaway" and search for the first entry
that claims to support deliveries. (I say "claims to support" because
marketing departments are often out of touch with reality.) Next, you
pick up the phone, and dial the appropriate number, without any
attempt to remember this number. You give the details of what you want
delivered. You wait for it to be delivered.
What's the difference, here? Well, there are several. First of all,
you have no idea what business you purchased your food from. Second,
your choice will be affected by the order in which the businesses are
listen in the directory, typically alphabetical, and not by previous
experience, food quality, or prices.
What on earth does this have to do with Perl?
Well, clearly you need to eat Pizza in order to code. Also, it just
struck me that this is in fact a good metaphor for the whole PLIF
thing that someone mentioned earlier. See Table 1.
Real Life | Perl Program
--------------------+-----------------------------------------
Telephone Call | Perl Method Call
Ordering Pizza | Processing Data
Business | A Perl Module
Domino's Pizza | A Specific Perl Module
Pizza | The Method Call Return Value
Front Door | Where The Method Call Returns Its Value
Holiday | Unexpected Environment
Business Directory | A List Of Perl Modules
Deliveries | A Particular Perl Method In A Module
--------------------+-----------------------------------------
Table 1: A mapping of the real life example to the perl program
equivalent, in case the metaphor wasn't blindingly obvious.
Let's be more specific. Say you have a record ID, and you want to get
the data that it refers to out of the database. For simplicity, we
will assume that our database merely associates each number with a
string. So. In the Old World, you would do something like:
SendSQL("select string from data where id = $id");
my $string;
if (@row = FetchSQLData()) {
$string = $row[0];
} else {
$string = '';
}
# do something with $string...
That has some flaws: for example, what happens when you want to change
from SQL queries to QBE queries? What about if the fields in the
database change name?
Instead, what you want to do is delegate the task of querying the
database to some other module, known as a "data source", and merely
concern yourself with getting said data from the data source. To do
this, you first need to get a hold of the data source. The problem is
that you have no idea what data source to use -- do you want the
default SQL database data source or the default database QBE data
source? What about if neither of these exist, but someone will provide
a third type that you don't know about yet?
So instead, you merely ask a central controlling entity -- a registry,
or directory, of all known data sources -- for the data source that
deals with the default database. You then call predefined methods in
the data source. The code would look something like:
my $string = $app->getService('dataSource.default')->getString($app, $id);
# do something with $string...
There are several things to notice here. First of all, to get hold of
the data source we said:
$app->getService('dataSource.default')
That tells us that $app is the controller -- that is to say, the
central registry of all data sources is the main application
object. More on this later. It also tells us that the method used to
get the data source is called "getService".
You may be asking yourself why it is called "getService" instead of
the more obvious "getDataSource".
Well, data sources are not the only thing that you might want to get a
hold of. All the input and output is done using this technique -- so
that the main code doesn't need to know it's talking to IRC or over
HTTP to do its work. More on this later.
The general term for all these different interfaces is "services".
Hence, the name of the method is "getService" -- it gets the
appropriate service. I tried making it more obvious, but it was hard,
so I gave up. There are several other methods that return services,
and they are explained in the chapter describing the workings of the
application object.
You should also notice that getService() gets passed a string -- that
string is used to determine whether or not each registered module
provides the service or not. ("Providing a service" is called
"implementing an interface" in COM terms, I believe.)
The string is generally opaque, although that depends on the
module. What I mean by "opaque" is that modules don't try to parse it
to work out whether or not to claim to support a particular service.
The next thing to notice is that getService() returns an object, and
that it is therefore directly used as such -- the method on the data
source is invoked straight off the return value of the getService()
call, and it is the results of the getString() call on the service
that is stored in $string.
So, in summary: If you want to do something that might be done in
several different ways and the code you are immediately dealing with
doesn't need to know the difference, then you would implement the
'something' as a Service and use the 'getService()' method on the
application object to get a reference to an instance of the service.
Questions raised by this:
1. How do you implement a service?
2. How do you use getService?
3. How do you get an application object?
4. How much should you tip the delivery guy?
We shall cover each of these questions, eventually. First, however,
I'm going to go on a totally different tangent because I am bored with
services now and what to talk about warnings and stuff.
______________________________
CHAPTER 3: PLIF ERROR HANDLING
| Wherein it is first claimed that PLIF has great tools for error
| handling but then that is shown to be totally untrue.
The root of (almost) every PLIF class is the "PLIF" class. What that
means is that at (almost) any point in PLIF-based code, you can use
methods that are part of the core PLIF class. Now, there aren't many
of them, so you'd better make the most of it!
The methods that are of interest to us right now are the following
five debugging aids:
dump(level, message)
Prints the message to standard error. The level argument is a
number, typically in the range of 0-9, stating the verbosity of the
message. Users of your application (as in, the people who install
it, not the people who use it on a daily basis) can change the
debugging level that is printed, so if you have a lot of
dump(9,'verbose debugging information') calls they can easily turn
them off. 0 is the most serious, 9 is the most trivial.
warn(level, message)
Same as dump(), but includes a stack trace.
error(level, message)
Same as warn(), but raises an exception as well. (You can catch
exceptions using eval{}.)
assert(condition, level, message)
Calls error() if condition is true.
notImplemented()
Calls error() with predefined arguments.
These tools are a great help. They should prevent you from ever
needing to use print() debugging, for instance. They allow you to
quickly wrap null pointer checks and the like in unobtrusive one
liners while supporting decent amounts of debugging information.
They also allow us to later reimplement the debugging code to add
better support for debuggers or pretty printing or mailing errors to
admins or whatever.
Unfortunately, using these utility methods to report errors can result
in suboptimal feedback to the user, and so should only be used to
report errors that you really were not expecting, such as missing
configuration files, errors sending mail, failures when connecting to
databases, and so on. For errors in user data, e.g. wrong password,
unknown requests, out of range input and the like, you want to report
the errors using the usual techniques of error codes and callbacks.
(Note. These debugging methods are _class methods_ and therefore you
do not need to ensure that $self is a reference before calling them.)
_________________________________
CHAPTER 4: IMPLEMENTING A SERVICE
| Wherein examples modules are provided on the grounds that they will
| enable the reader to learn how to create modules on their own, but
| with the knowledge that in practice the said examples will only be
| used for the purposes of copy and pasting.
Implementing a service is relatively easy. To demonstrate this, we
shall be implementing a "vendingMachine" service. First, we need to
define what we mean by a "vendingMachine" service, then we need to
define the API, and finally we shall implement it.
Concept Definition. You have to decide when you expect to use the
service -- in this case, it will be called by other parts of the
application when they need some food. The name of the service is
important. In this case, it's just a generic "vendingMachine", but
subtypes could include variants called "vendingMachine.drinks" or
"vendingMachine.sweets", for instance. One example of this in the PLIF
code is all the "dataSource.X" services, which all implement a basic
set of functionality that is used by other parts of the code when they
are passed a data source without knowning what it is.
API Definition. Now, having decided what we think the service is for,
we come to the second step, namely defining the API. This is just as
hard, and in my experience it takes a lot of attempts before you have
one you are happy with.
We're going to say that "vendingMachine" offers these methods:
insertCoins(amount)
Increases the amount of money assumed to be inside the vending
machine. Returns the result amount of cash.
selectSlot(slot)
Decreases the amount of money assumed to be inside the vending
machine and returns a string describing the product that occupied
the slot specified. Returns undef if there was not enough money.
refund()
Returns the amount of money in the machine, and sets it to zero.
Implementation. This is the fun part. Depending on the service, it can
also be the easiest.
I write my Perl modules in Emacs, so first I have a mode line:
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
Next comes the license, in this case MPL/GPL:
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
At last, some code! The first line is the Perl code saying what the
name of the package is:
package VendingMachine::Empty;
Hmm. It appears I've opted for implementing the Empty version of the
service. This ought the be fun. Next comes a bit of standard stuff:
use strict;
use vars qw(@ISA);
That's always there. "use strict" ensures that we avoid the worst of
ugly Perl, and "use vars" is required by the "use strict".
Next, we need to define what module we are inheriting from.
use PLIF::Service;
@ISA = qw(PLIF::Service);
All services must inherit from PLIF::Service or a descendant of that
module (e.g. VendingMachine::Empty!).
1;
This ensures that this module will return true. It's a Perlism.
Ok, finally the real meat. We have to claim that we provide the
vending machine service! This is done using a "provides" method:
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'vendingMachine' or $class->SUPER::provides($service));
}
What this does is return true if the caller asked if we provide a
"vendingMachine" service, and otherwise it defers to the inherited
method. You'll notice this is a class method -- at this point, the
$class variable is probably a class and not necessarily an object.
Next we implement a constructor. (This is actually a method called by
the constructor. Just treat it like a constructor in other languages
and you'll be fine.) We need a constructor because we need to
initialise the amount of money to zero (as opposed to undefined).
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($app) = @_;
$self->money(0);
}
Wowee, lots of PLIFisms there! Let's look at each one in turn. The
first line of the body sets the $self variable to be the reference to
the object. If you are familiar with JavaScript, think "this".
The second line calls the inherited constructor with the same
arguments as was passed to _this_ constructor.
Speaking of which, the arguments are sorted out on the third
line. Most services will be given just one argument on construction,
namely a reference to the application. It is vital that services not
hold on to this! See the Weak References chapter for more details.
Finally, the fourth line is pure fun. Due to some magical fu described
in a later chapter, you can use the syntax shown to set a "field" of
the object to 0. You can also get the value using a call without any
arguments, as in "$self->money". More on this later.
Ok, so now we have to implement the methods that we claim to provide
by saying that we are a vending machine.
# Increases the amount of money assumed to be inside the vending
# machine. Returns the resulting amount of cash.
sub insertCoins {
my $self = shift;
my($money) = @_;
return $self->money($self->money + $money);
}
That method should be self-explanatory... First it sets $self, then it
sorts out the arguments (in this case just one, $money) and then it
uses the syntax described above to add $money to $self->money, which
it returns.
# Decreases the amount of money assumed to be inside the vending
# machine and returns a string describing the product that
# occupied the slot specified. Returns undef if there was not
# enough money.
sub selectSlot {
my $self = shift;
my($slot) = @_;
return undef;
}
The vending machine is empty, right? So that always return undef.
Finally, refund() -- lucky we are going to implement this, otherwise
people could never get their money back!
# Returns the amount of money in the machine, and sets it to zero.
sub refund {
my $self = shift;
my $money = $self->money;
$self->money(0);
return $money;
}
Ok! We have an implementation of a service!
In the next chapter we shall look at how to use it.
.############################## Everything above this line has
#################### BOOK MARK # already been sent to mozilla-webtools
'############################## in some form or another.
____________________________
CHAPTER 5: USING GET SERVICE
| Wherein a family of methods is brought to the front and examined as
| if for a college entrance exam, resulting in the discovery that one
| of the methods is not very bright.
At this point I shall mention that some services get more than just
the $app as an argument on construction...
__________________________________
CHAPTER 6: THE MAGIC OF PROPERTIES
| Wherein it is admitted that the last description of the PLIF class
| was incomplete and was missing some rather important facts.
propertyGet, propertySet, and friends.
__________________________
CHAPTER 7: WEAK REFERENCES
| or, Why The $app Variable Is Passed Religiously From Service To
| Service Without A Thought To Caching It and Why It Would Be Bad To
| Do Otherwise.
You no copy you go boom boom.
______________________________________
CHAPTER 8: THE MAIN APPLICATION OBJECT
| Wherein the reader is introduced to the concept of magic and is then
| walked through the steps of taming the magic for his own purposes.
Or not.
___________________________
CHAPTER 9: INPUT AND OUTPUT
| Wherein the magic of $app->input and $app->output is explained.
Some day.
_____________________
CHAPTER n: CONCLUSION
| Wherein it is revealed that all is subject to change, only available
| while stocks last, and void where prohibited by law.
The End.

View File

@ -0,0 +1,268 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF;
use strict; # require strict adherence to perl standards
use vars qw($AUTOLOAD); # it's a package global
use Carp qw(cluck confess); # stack trace versions of warn and die
my $DEBUG = 9; # level of warnings and dumps to print to STDERR (none go to user)
my $USER = 3; # level of errors to report to user (all go to STDERR)
my @FATAL = (); # a list of pointers to functions that want to report errors to the user
my $LOCKED = 0; # set to '1' while we are calling the error reporting code
1;
# PLIF = Program Logic Insulation Framework
# Levels are assumed to be something along the following:
# 0 = total failure: e.g. no input or output devices
# 1 = fatal errors: e.g. missing databases, broken connections, out of disk space
# 2 = security: e.g. warnings about repeated cracking attempts
# 3 = non-fatal errors: e.g. propagation of eval() errors as warnings
# 4 = important warnings (e.g. unexpected but possibly legitimate lack of data)
# 5 = important events (e.g. application started)
# 6 =
# 7 = typical checkpoints (e.g. someone tried to do some output)
# 8 =
# 9 = verbose debugging information
# 10 = ridiculously verbose debugging spam
# Note. All of the methods described in this class except for the
# propertyGet, propertySet and propertyExists methods are class
# methods. You can call "$class->notImplemented" without a problem.
# provide a standard virtual constructor
# if already created, merely return $self
sub create {
my $class = shift;
if (ref($class)) {
return $class; # already created, return self
} else {
return $class->bless(@_); # call our real constructor
}
}
# provide a constructor that always constructs a new copy of the
# class. This is used by services that implement factories for objects
# implemented in the same class (e.g., session objects do this).
sub bless {
my $class = shift;
if (ref($class)) {
$class = ref($class);
}
my $self = {};
CORE::bless($self, $class);
$self->init(@_);
return $self;
}
sub init {} # stub
# provide method-like access for any scalars in $self
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/^.*://o; # strip fully-qualified portion
if ($self->propertyImpliedAccessAllowed($name)) {
if (scalar(@_) == 1) {
$self->dump(10, "setting implied property $name in $self");
return $self->propertySet($name, @_);
} elsif (scalar(@_) == 0) {
if ($self->propertyExists($name)) {
$self->dump(10, "getting implied property $name in $self");
return $self->propertyGet($name);
} else {
$self->dump(10, "not getting non-existent implied property $name in $self");
return $self->propertyGetUndefined($name);
}
}
$self->dump(10, "neither setting nor getting implied property $name in $self");
} else {
$self->dump(10, "not treating $name in $self as an implied property, regardless of its existence");
}
$self->methodMissing($AUTOLOAD);
}
sub propertySet {
# this is not a class method
my $self = shift;
my($name, $value) = @_;
return $self->{$name} = $value;
}
sub propertyExists {
# this is not a class method
my $self = shift;
my($name) = @_;
$self->assert($name, 0, 'propertyExists() cannot be called without arguments');
$self->dump(10, "checking for existence of property $name in $self");
return exists($self->{$name});
}
sub propertyImpliedAccessAllowed {
# this is not (supposed to be) a class method
# my $self = shift;
# my($name) = @_;
# $self->assert($name, 0, 'propertyImpliedAccessAllowed() cannot be called without arguments');
return 1;
}
sub propertyGet {
# this is not a class method
my $self = shift;
my($name) = @_;
return $self->{$name};
}
sub propertyGetUndefined {
return undef;
}
sub methodMissing {
my $self = shift;
my($method) = @_;
$self->error(0, "Internal Error: Tried to access non-existent method $method in object $self");
}
# DEBUGGING AIDS
sub dump {
my $self = shift;
my($level, @data) = @_;
if ($self->isAtDebugLevel($level)) {
foreach (@data) {
print STDERR "$0: $_\n";
}
}
}
sub warn {
my $self = shift;
my($level, @data) = @_;
if ($self->isAtDebugLevel($level)) {
$self->dump($level, ('-'x12).' Start of Warning Stack Trace '.('-'x12));
cluck(@data); # warn with stack trace
$self->dump($level, ('-'x12). ('-'x30) .('-'x12));
}
}
sub error {
my $self = shift;
my($level, @data) = @_;
$self->dump(9, "error raised: $data[0]");
if ($self->isAtUserLevel($level) and not $LOCKED) {
$LOCKED = 1;
$self->dump(10, 'calling @FATAL error handlers...');
foreach my $entry (@FATAL) {
eval {
&{$entry->[1]}(@data);
};
if ($@) {
$self->warn(3, 'Error occured during \@FATAL callback of object \''.($entry->[0])."': $@");
}
}
$self->dump(10, 'done calling @FATAL error handlers');
$LOCKED = 0;
}
confess(@data); # die with stack trace
}
sub assert {
my $self = shift;
my($condition, $level, @data) = @_;
if (not $condition) {
$self->error($level, @data);
}
}
sub notImplemented {
my $self = shift;
$self->error(0, 'Internal Error: Method not implemented');
}
# returns true only if the argument is a debug level that is at least
# as important as the local value of $DEBUG.
sub isAtDebugLevel {
my $self = shift;
my($level) = @_;
return ($level <= $DEBUG);
}
# returns true only if the argument is a debug level that is at least
# as important as the local value of $USER.
sub isAtUserLevel {
my $self = shift;
my($level) = @_;
return ($level <= $USER);
}
# returns a reference to the $DEBUG variable for configuration
# purposes
sub getDebugLevel {
return \$DEBUG;
}
# returns a reference to the $USER variable for configuration purposes
sub getUserLevel {
return \$USER;
}
# returns a reference to the @FATAL variable for modules that have
# very exotic needs
sub getFatalHandlerList {
return \@FATAL;
}
# returns a reference to the $LOCKED variable for modules that which
# to block @FATAL reporting
sub getFatalHandlerLock {
return \$LOCKED;
}
# if you call this, make sure that you call the next function too,
# guarenteed, otherwise you will never be freed until the app dies.
# of course, if you _are_ the app then I guess it's ok...
sub enableErrorReporting {
my $self = shift;
push(@FATAL, [$self, sub { $self->fatalError(@_); }]);
}
sub disableErrorReporting {
my $self = shift;
my @OLDFATAL = @FATAL;
@FATAL = ();
foreach my $entry (@OLDFATAL) {
if ($entry->[0] != $self) {
push(@FATAL, $entry);
}
}
}
sub fatalError {} # stub
sub DESTROY {} # stub

View File

@ -0,0 +1,98 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Application;
use strict;
use vars qw(@ISA);
@ISA = qw(PLIF::Program);
require PLIF::Program; # see note below
1;
# Note: this module "require"s PLIF::Program (as opposed to "use"ing
# it) because that module will try to call 'main'->create, which won't
# work if the module is parsed during compilation instead of during
# execution. For the same reason, the @ISA line is above the
# require. All modules that have PLIF::Application as an ancestor need
# to do this.
#
# In theory, if you use PLIF::Application, the class tree should look
# like this:
#
# PLIF (the core module)
# |
# PLIF::Controller (defines the service management)
# |
# PLIF::Program (defines things like 'input' and 'output')
# |
# PLIF::Application (defines the generic command dispatcher)
# |
# A PLIF Shell (bootstraps PLIF::Application)
#
# However, you might want to skip the PLIF::Application layer if all
# you are doing is writing a `simple' utility (although frankly I
# would doubt your choice of PLIF as an infrastructure if all you are
# looking for is a `simple' utility -- HTTP content negotiation and
# database-agnostic logic is probably a bit of an overkill there...).
#
# If you are writing an application that uses PLIF for some part of
# the work, but not for input and output, then you would probably
# inherit straight from PLIF::Controller, and only use the getService
# call (and friends).
# find either a service or a one-shot object that claims to implement
# command dispatching, and ask them to handle this.
sub dispatch {
my $self = shift;
my($command) = @_;
if (not ($self->getSelectingObjectList('commands.dispatcher')->dispatch($self, $command) or
$self->getSelectingObjectList('commands.dispatcher')->dispatch($self, $command))) {
$self->SUPER::dispatch(@_);
}
}
sub registerServices {
my $self = shift;
$self->SUPER::registerServices(@_);
$self->registerDefaultServices();
$self->registerInstalledServices();
}
sub registerDefaultServices {
my $self = shift;
# install the configuration system
$self->register(qw(PLIF::DataSource::Configuration PLIF::Database::ConfigurationFile));
}
sub registerInstalledServices {
my $self = shift;
# install the modules from the configuration database
my $modules = $self->getService('dataSource.configuration')->getInstalledModules($self);
if (defined($modules)) {
$self->register(@$modules);
}
}

View File

@ -0,0 +1,194 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Controller;
use strict;
use vars qw(@ISA);
use PLIF;
use PLIF::MagicPipingArray;
use PLIF::MagicSelectingArray;
@ISA = qw(PLIF);
1;
# setup everything (typically called from the constructor)
sub init {
my $self = shift;
$self->SUPER::init(@_);
# initialise our app name to be the name of the executable
$self->name($0); # may be overridden by descendants
# prepare the services array for the registration system
$self->services([]);
# perform the registration
$self->registerServices();
}
# should be called from the implementation of registerServices, should
# be passed a list similar to the @ISA list. The order matters, since
# services will be instantiated on a first-matched first-used basis
sub register {
my $self = shift;
foreach my $service (@_) {
push(@{$self->services}, $service);
my $file = $service;
# XXX THIS IS PLATFORM SPECIFIC CODE XXX
if ($^O eq 'linux') {
$file =~ s/::/\//go;
$file .= '.pm';
} else {
$self->error(0, "Platform '$^O' not supported yet.");
}
# XXX END OF PLATFORM SPECIFIC CODE XXX
eval {
require $file;
};
if ($@) {
$self->error(1, "Compile error in $file: $@");
}
}
}
# helper method for input verifiers to add instantiated service
# objects specific to the current state (e.g. the current user in an
# event loop). These should be wiped out when the state changes
# (e.g. at the start of an event loop).
sub addObject {
my $self = shift;
foreach my $object (@_) {
push(@{$self->objects}, $object);
}
}
sub getService {
my $self = shift;
my($name) = @_;
foreach my $service (@{$self->services}) {
if ($service->provides($name)) {
# Create the service. If it is already created, this will
# just return the object reference, so no harm done.
# IT IS ABSOLUTELY IMPERATIVE THAT NO SERVICE EVER HOLD ON
# TO THE $self ARGUMENT PASSED TO THE CONSTRUCTOR!
# Doing so would create a circular dependency, resulting
# in a memory leak.
$service = $service->create($self);
return $service;
}
}
return undef;
}
sub getObject {
# same as getService but on the objects list and without the
# constructor call
my $self = shift;
my($name) = @_;
foreach my $service (@{$self->objects}) {
if ($service->provides($name)) {
return $service;
}
}
return undef;
}
sub getServiceList {
my $self = shift;
my($name) = @_;
my @services;
foreach my $service (@{$self->services}) {
if ($service->provides($name)) {
# Create the service. If it is already created, this will
# just return the object reference, so no harm done.
# IT IS ABSOLUTELY IMPERATIVE THAT NO SERVICE EVER HOLD ON
# TO THE $self ARGUMENT PASSED TO THE CONSTRUCTOR!
# Doing so would create a circular dependency, resulting
# in a memory leak.
push(@services, $service->create($self));
}
}
return @services;
}
sub getObjectList {
# same as getServiceList but on the objects list and without the
# constructor call
my $self = shift;
my($name) = @_;
my @services;
foreach my $service (@{$self->objects}) {
if ($service->provides($name)) {
push(@services, $service);
}
}
return @services;
}
sub getSelectingServiceList {
my $self = shift;
return PLIF::MagicSelectingArray->create($self->getServiceList(@_));
}
sub getSelectingObjectList {
my $self = shift;
return PLIF::MagicSelectingArray->create($self->getObjectList(@_));
}
sub getPipingServiceList {
my $self = shift;
return PLIF::MagicPipingArray->create($self->getServiceList(@_));
}
sub getPipingObjectList {
my $self = shift;
return PLIF::MagicPipingArray->create($self->getObjectList(@_));
}
sub getServiceInstance {
my $self = shift;
my($name, @data) = @_;
foreach my $service (@{$self->services}) {
if ($service->provides($name)) {
# Create and return the service instance, without storing
# a copy.
# This is the only time it is safe for a service to store
# a reference to us. This is because here no reference to
# the service is being held by us, so the moment the
# service goes out of scope, it will be freed.
# IMPORTANT! DON'T HOLD ON TO A SERVICE INSTANCE OBJECT!
local $" = '\', \'';
return $service->create($self, @data);
}
}
return undef;
}
# there's no getObjectInstance since objects already are instances...
# Implementation Specific Methods
# These should be overriden by real programs
sub registerServices {} # stub

View File

@ -0,0 +1,88 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::DataSource;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub database {
my $self = shift;
my($app) = @_;
# do we have a cached, checked copy?
if (defined($self->{'_database'})) {
# yes, return it
return $self->{'_database'};
}
# no, find the relevant database and return it
my @databases = $app->getServiceList('database.'.$self->databaseName);
foreach my $database (@databases) {
foreach my $type ($self->databaseType) {
if ($type eq $database->type) {
$self->{'_database'} = $database;
return $database;
}
}
}
$self->error(1, 'Configuration Error: There is no suitable \''.$self->databaseName.'\' database installed.');
}
sub helper {
my $self = shift;
my($app) = @_;
# do we have a cached, checked copy?
if (defined($self->{'_helper'})) {
# yes, return it
return $self->{'_helper'};
}
# no, find the relevant database helper and return it
my @helpers = $app->getServiceList('database.helper');
foreach my $helper (@helpers) {
foreach my $helperType ($helper->databaseType) {
foreach my $sourceType ($self->databaseType) {
if ($helperType eq $sourceType) {
$self->{'_helper'} = $helper;
return $helper;
}
}
}
}
$self->error(1, 'Configuration Error: There is no database helper suitable for the \''.$self->databaseName.'\' database installed.');
}
sub databaseName {
my $self = shift;
$self->notImplemented();
}
sub databaseType {
my $self = shift;
$self->notImplemented();
}

View File

@ -0,0 +1,109 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::DataSource::Configuration;
use strict;
use vars qw(@ISA);
use PLIF::DataSource;
@ISA = qw(PLIF::DataSource);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'dataSource.configuration' or $service eq 'setup.configure' or $class->SUPER::provides($service));
}
sub databaseName {
return 'configuration';
}
sub databaseType {
return 'property';
}
# Configuration API Implementation Follows
sub configurationFilename {
return '.PLIF';
}
sub setupConfigure {
my $self = shift;
my($app) = @_;
$self->dump(9, 'about to configure Configuration data source...');
eval {
# if it failed earlier but without crashing the app, then it
# will fail again (we only stop trying once it works)
$self->database($app)->ensureRead();
};
if ($@) {
# well, that didn't go down too well. Let's create a brand
# spanking new configuration file, since they clearly don't
# have one.
$self->database($app)->assumeRead(); # new file at the ready
# options should now be set by the users of the datasource.
}
$self->dump(9, 'done configuring Configuration data source');
return; # no problems
}
sub getInstalledModules {
my $self = shift;
my($app) = @_;
return $self->database($app)->propertyGet('PLIF.modulesList');
}
sub setInstalledModules {
my $self = shift;
my($app, $value) = @_;
$self->database($app)->propertySet('PLIF.modulesList', $value);
}
sub getDBIDatabaseSettings {
my $self = shift;
my($app, $database) = @_;
my $configuration = $self->database($app);
my $prefix = 'database.'.$database->class;
foreach my $property ($database->settings) {
my $value = $configuration->propertyGet("$prefix.$property");
$self->assert($value, 1, "The configuration is missing a valid value for '$prefix.$property'");
$database->propertySet($property, $value);
}
}
sub setDBIDatabaseSettings {
my $self = shift;
my($app, $database) = @_;
my $configuration = $self->database($app);
my $prefix = 'database.'.$database->class;
foreach my $property ($database->settings) {
$configuration->propertySet("$prefix.$property", $database->propertyGet($property));
}
}

View File

@ -0,0 +1,178 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::DataSource::Strings;
use strict;
use vars qw(@ISA);
use PLIF::DataSource;
use HTTP::Negotiate; # DEPENDENCY
use HTTP::Headers; # DEPENDENCY
@ISA = qw(PLIF::DataSource);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'dataSource.strings' or $service eq 'setup.install' or $class->SUPER::provides($service));
}
sub init {
my $self = shift;
$self->SUPER::init(@_);
$self->variantsCache({});
}
sub databaseName {
return 'default';
}
sub get {
my $self = shift;
my($app, $session, $protocol, $string) = @_;
# error handling makes code ugly :-)
my $variant;
if (defined($session)) {
$variant = $session->selectVariant($app, $protocol);
}
if (not defined($variant)) {
# default session or $session didn't care, get stuff from
# $app->input instead
$variant = $self->selectVariant($app, $protocol);
}
my $result;
eval {
$self->getString($app, $variant, $string);
};
if ($@) {
# ok, so, er, it seems that didn't go to well
# XXX do we want to do an error here or something?
$self->warn(4, "While I was looking for the string '$string' in protocol '$protocol' using variant '$variant', I failed with: $@");
}
if (not defined($result)) {
$result = $self->getDefaultString($app, $protocol, $string);
$self->assert($result, 1, "Couldn't find a string to display for '$string' in protocol '$protocol'");
}
return $result;
}
sub selectVariant {
my $self = shift;
my($app, $protocol) = @_;
# Find list of options from DB.
my $variants = $self->variants($app, $protocol);
# Initialize the fake header
my $request = new HTTP::Headers;
foreach my $header (['Accept', $self->acceptType($app, $protocol)],
['Accept-Encoding', $self->acceptEncoding($app, $protocol)],
['Accept-Charset', $self->acceptCharset($app, $protocol)],
['Accept-Language', $self->acceptLanguage($app, $protocol)]) {
# only add headers that exist -- HTTP::Negotiate isn't very bullet-proof :-)
if ($header->[1]) {
$request->header(@$header);
}
}
# Do Content Negotiation :-D
my $choice;
if (scalar(@$variants) > 0) {
# $HTTP::Negotiate::DEBUG = 1; # enable debugging
$choice = choose($variants, $request);
}
if (not defined($choice)) {
$choice = 0; # XXX we could maybe not hard code the default variant some how... ;-)
}
return $choice;
}
# Variants returns an arrayref or arrayrefs, typically to be passed to
# HTTP::Negotiate, containing:
# variant id, quality, content type, encoding, character set,
# language, size
# Note that we don't support 'size', since doing so would require the
# unbelivably slow operation of calculating the length of the every
# possible string for everyone. No thanks. ;-)
sub variants {
my $self = shift;
my($app, $protocol) = @_;
if (not defined($self->variantsCache->{$protocol})) {
eval {
$self->variantsCache->{$protocol} = $self->getVariants($app, $protocol);
};
if ($@) {
# ok, so, er, it seems that didn't go to well
# XXX do we want to do an error here or something?
$self->warn(4, "Just so you know, I'm going to silently ignore the fact that I completely failed to get any variants... For what it's worth, the error was: $@");
return []; # no variants here, no sir!
}
}
return $self->variantsCache->{$protocol};
}
sub acceptType {
my $self = shift;
my($app, $protocol) = @_;
return $app->input->acceptType;
}
sub acceptEncoding {
my $self = shift;
my($app, $protocol) = @_;
return $app->input->acceptEncoding;
}
sub acceptCharset {
my $self = shift;
my($app, $protocol) = @_;
return $app->input->acceptCharset;
}
sub acceptLanguage {
my $self = shift;
my($app, $protocol) = @_;
return $app->input->acceptLanguage;
}
sub getString {
my $self = shift;
$self->notImplemented();
}
sub getDefaultString {
my $self = shift;
my($app, $protocol, $string) = @_;
return $app->getSelectingServiceList('dataSource.strings.default')->get($app, $protocol, $string);
}
sub getVariants {
my $self = shift;
$self->notImplemented();
}
sub setupInstall {
my $self = shift;
$self->notImplemented();
}

View File

@ -0,0 +1,88 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::DataSource::Strings::MySQL;
use strict;
use vars qw(@ISA);
use PLIF::DataSource::Strings;
@ISA = qw(PLIF::DataSource::Strings);
1;
sub databaseType {
return qw(mysql);
}
sub getString {
my $self = shift;
my($app, $variant, $string) = @_;
return $self->database($app)->execute("SELECT data FROM strings WHERE variant = ? string = ?", $variant, $string)->rows;
}
sub getVariants {
my $self = shift;
my($app, $protocol) = @_;
return $self->database($app)->execute("SELECT id, quality, type, encoding, charset, language FROM variants WHERE protocol = ?", $protocol)->rows;
}
sub setupInstall {
my $self = shift;
my($app) = @_;
my $helper = $self->helper($app);
if (not $helper->tableExists($app, $self->database($app), 'stringVariants')) {
$self->database($app)->execute('
CREATE TABLE stringVariants (
id integer unsigned auto_increment not null primary key,
name varchar(255) not null,
protocol varchar(255) not null,
encoding varchar(255),
type varchar(255) not null,
charset varchar(255),
language varchar(255) not null,
quality float not null default 1.0,
description text,
translator varchar(255),
unique index (name)
);
');
} else {
# check its schema is up to date
}
if (not $helper->tableExists($app, $self->database($app), 'strings')) {
$self->database($app)->execute('
CREATE TABLE strings (
variant integer unsigned not null,
name varchar(32) not null,
data text,
primary key (variant, name)
);
');
} else {
# check its schema is up to date
}
return;
}

View File

@ -0,0 +1,49 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Database;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'database.'.$class->class or $class->SUPER::provides($service));
}
sub class {
return 'undefined';
}
sub type {
my $self = shift;
return $self->SUPER::type(@_);
}

View File

@ -0,0 +1,145 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Database::ConfigurationFile;
use strict;
use vars qw(@ISA);
use PLIF::Database;
use Data::Dumper; # DEPENDENCY
@ISA = qw(PLIF::Database);
1;
# WARNING XXX
# Reading without create a file first will FAIL!
#
# You must run the equivalent of an installer program to ensure the
# configuration file exists
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($app) = @_;
$self->{'_FILENAME'} = $app->getService('dataSource.configuration')->configurationFilename;
}
sub class {
return 'configuration';
}
sub type {
return 'property';
}
sub filename {
my $self = shift;
return $self->{'_FILENAME'};
}
# typically uou won't call this directly, but will ese ensureRead below.
sub read {
my $self = shift;
$self->{'_DIRTY'} = undef; # to prevent recursion: eval -> propertySet -> ensureRead (dirty check) -> read -> eval
my $filename = $self->filename;
local *FILE; # ugh
$self->assert(open(FILE, "<$filename"), 1, "Could not open configuration file '$filename' for reading: $!");
local $/ = undef; # slurp entire file (no record delimiter)
my $settings = <FILE>;
$self->assert(close(FILE), 3, "Could not close configuration file '$filename': $!");
if ($settings) {
$settings =~ /^(.*)$/so;
eval($1); # untaint the configuration file
$self->assert(defined($@), 1, "Error processing configuration file '$filename': $@");
}
$self->{'_DIRTY'} = 0;
}
# reads the database unless that was already done
sub ensureRead {
my $self = shift;
if (not exists($self->{'_DIRTY'})) {
# not yet read configuration
$self->read();
}
}
# don't call this unless you know very well what you are doing
# it basically results in the file being overwritten (if you
# call it before using propertyGet, anyway)
sub assumeRead {
my $self = shift;
$self->{'_DIRTY'} = 0;
}
# typically you won't call this directly, but will just rely on the
# DESTROY handler below.
sub write {
my $self = shift;
my $filename = $self->filename;
local *FILE; # ugh
$self->assert(open(FILE, ">$filename"), 1, "Could not open configuration file '$filename' for writing: $!");
$self->assert(FILE->print("# This is the configuration file.\n# You may edit this file, so long as it remains valid Perl.\n"), 1,
"Could not store leading comments in '$filename': $!");
local $Data::Dumper::Terse = 1;
foreach my $variable (sort(keys(%$self))) {
if ($variable !~ /^_/o) { # we skip the internal variables (prefixed with '_')
my $contents = Data::Dumper->Dump([$self->{$variable}]);
chop($contents); # remove the newline
$self->assert(FILE->print("\$self->propertySet('$variable', $contents);\n"), 1,
"Could not dump variable '$variable' to configuration file '$filename': $!");
}
}
$self->assert(close(FILE), 1, "Could not close configuration file '$filename': $!");
$self->{'_DIRTY'} = 0;
}
sub propertySet {
my $self = shift;
$self->ensureRead();
my $result = $self->SUPER::propertySet(@_);
$self->{'_DIRTY'} = 1;
return $result;
}
sub propertyExists {
my $self = shift;
$self->ensureRead();
return $self->SUPER::propertyExists(@_);
}
sub propertyGet {
my $self = shift;
$self->ensureRead();
return $self->SUPER::propertyGet(@_);
}
sub DESTROY {
my $self = shift;
if ($self->{'_DIRTY'}) {
$self->write();
}
}

View File

@ -0,0 +1,131 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Database::DBI;
use strict;
use vars qw(@ISA);
use PLIF::Database;
use PLIF::Database::ResultsFrame::DBI;
use DBI; # DEPENDENCY
@ISA = qw(PLIF::Database);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'setup.configure' or $class->SUPER::provides($service));
}
# the name used to identify this database in the configuration
sub class {
return 'default';
}
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($app) = @_;
eval {
$self->getConfig($app);
my $type = $self->type;
my $name = $self->name;
my $host = $self->host;
my $port = $self->port;
$self->handle(DBI->connect("DBI:$type:$name:$host:$port",
$self->username, $self->password,
{RaiseError => 0, PrintError => 0, AutoCommit => 1}));
$self->errstr($DBI::errstr);
$self->dump(9, 'tried to connect to database without raising an exception!');
};
if ($@) {
$self->handle(undef);
$self->errstr($@);
$self->dump(9, "failed to connect to the database because of $@");
}
}
sub settings {
return qw(type name host port username password);
}
sub propertyGetUndefined {
my $self = shift;
my($name) = @_;
foreach my $property ($self->settings) {
if ($name eq $property) {
return '';
}
}
return $self->SUPER::propertyGetUndefined(@_);
}
sub execute {
my $self = shift;
my($statement, @values) = @_; # XXX does this not need $app to be passed?
$self->assert($self->handle, 1, 'No database handle: '.(defined($self->errstr) ? $self->errstr : 'unknown error'));
my $handle = $self->handle->prepare($statement);
$handle->execute(@values);
return PLIF::Database::ResultsFrame::DBI->create($handle); # XXX no app?
}
sub getConfig {
my $self = shift;
my($app) = @_;
$app->getService('dataSource.configuration')->getDBIDatabaseSettings($app, $self);
}
sub setupConfigure {
my $self = shift;
my($app) = @_;
$self->dump(9, 'about to configure DBI...');
my $prefix = 'database.'.$self->class;
foreach my $property ($self->settings) {
# XXX need to be able to offer current configuration as default values!
if (not $self->propertyExists($property)) {
my $value = $app->input->getArgument("$prefix.$property");
$self->dump(9, "Setting value '$property' to '$value'");
if (defined($value)) {
$self->propertySet($property, $value);
} else {
return "$prefix.$property";
}
}
}
$app->getService('dataSource.configuration')->setDBIDatabaseSettings($app, $self);
$self->dump(9, 'done configuring DBI...');
return;
}
sub DESTROY {
my $self = shift;
if ($self->handle) {
$self->handle->disconnect();
$self->handle(undef);
}
$self->SUPER::DESTROY(@_);
}

View File

@ -0,0 +1,57 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Database::ResultsFrame::DBI;
use strict;
use vars qw(@ISA);
use PLIF;
use DBI;
@ISA = qw(PLIF);
1;
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($handle) = @_;
$self->handle($handle);
}
sub row {
my $self = shift;
return $self->handle->fetchrow_array();
}
sub rows {
my $self = shift;
return $self->handle->fetchall_arrayref();
}
# other possible APIs:
# $ary_ref = $sth->fetchrow_arrayref;
# $hash_ref = $sth->fetchrow_hashref;

View File

@ -0,0 +1,45 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::DatabaseHelper;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'database.helper' or $class->SUPER::provides($service));
}
sub databaseType {
my $self = shift;
$self->notImplemented();
}

View File

@ -0,0 +1,213 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::DatabaseHelper::DBI;
use strict;
use vars qw(@ISA);
use PLIF::DatabaseHelper;
@ISA = qw(PLIF::DatabaseHelper);
1;
sub databaseType {
return qw(mysql);
}
=over time i would expect the following to be implemented:
###########################################################################
# Detect changed local settings
###########################################################################
sub GetFieldDef ($$)
{
my ($table, $field) = @_;
my $sth = $dbh->prepare("SHOW COLUMNS FROM $table");
$sth->execute;
while (my $ref = $sth->fetchrow_arrayref) {
next if $$ref[0] ne $field;
return $ref;
}
}
sub GetIndexDef ($$)
{
my ($table, $field) = @_;
my $sth = $dbh->prepare("SHOW INDEX FROM $table");
$sth->execute;
while (my $ref = $sth->fetchrow_arrayref) {
next if $$ref[2] ne $field;
return $ref;
}
}
sub CountIndexes ($)
{
my ($table) = @_;
my $sth = $dbh->prepare("SHOW INDEX FROM $table");
$sth->execute;
if ( $sth->rows == -1 ) {
die ("Unexpected response while counting indexes in $table:" .
" \$sth->rows == -1");
}
return ($sth->rows);
}
sub DropIndexes ($)
{
my ($table) = @_;
my %SEEN;
# get the list of indexes
#
my $sth = $dbh->prepare("SHOW INDEX FROM $table");
$sth->execute;
# drop each index
#
while ( my $ref = $sth->fetchrow_arrayref) {
# note that some indexes are described by multiple rows in the
# index table, so we may have already dropped the index described
# in the current row.
#
next if exists $SEEN{$$ref[2]};
my $dropSth = $dbh->prepare("ALTER TABLE $table DROP INDEX $$ref[2]");
$dropSth->execute;
$dropSth->finish;
$SEEN{$$ref[2]} = 1;
}
}
#
# Check if the enums in the bugs table return the same values that are defined
# in the various locally changeable variables. If this is true, then alter the
# table definition.
#
sub CheckEnumField ($$@)
{
my ($table, $field, @against) = @_;
my $ref = GetFieldDef($table, $field);
#print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n";
$_ = "enum('" . join("','", @against) . "')";
if ($$ref[1] ne $_) {
print "Updating field $field in table $table ...\n";
$_ .= " NOT NULL" if $$ref[3];
$dbh->do("ALTER TABLE $table
CHANGE $field
$field $_");
$::regenerateshadow = 1;
}
}
###########################################################################
# Update the tables to the current definition
###########################################################################
#
# As time passes, fields in tables get deleted, added, changed and so on.
# So we need some helper subroutines to make this possible:
#
sub ChangeFieldType ($$$)
{
my ($table, $field, $newtype) = @_;
my $ref = GetFieldDef($table, $field);
#print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n";
my $oldtype = $ref->[1];
if ($ref->[4]) {
$oldtype .= qq{ default "$ref->[4]"};
}
if ($oldtype ne $newtype) {
print "Updating field type $field in table $table ...\n";
print "old: $oldtype\n";
print "new: $newtype\n";
$newtype .= " NOT NULL" if $$ref[3];
$dbh->do("ALTER TABLE $table
CHANGE $field
$field $newtype");
}
}
sub RenameField ($$$)
{
my ($table, $field, $newname) = @_;
my $ref = GetFieldDef($table, $field);
return unless $ref; # already fixed?
#print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n";
if ($$ref[1] ne $newname) {
print "Updating field $field in table $table ...\n";
my $type = $$ref[1];
$type .= " NOT NULL" if $$ref[3];
$dbh->do("ALTER TABLE $table
CHANGE $field
$newname $type");
}
}
sub AddField ($$$)
{
my ($table, $field, $definition) = @_;
my $ref = GetFieldDef($table, $field);
return if $ref; # already added?
print "Adding new field $field to table $table ...\n";
$dbh->do("ALTER TABLE $table
ADD COLUMN $field $definition");
}
sub DropField ($$)
{
my ($table, $field) = @_;
my $ref = GetFieldDef($table, $field);
return unless $ref; # already dropped?
print "Deleting unused field $field from table $table ...\n";
$dbh->do("ALTER TABLE $table
DROP COLUMN $field");
}
=cut

View File

@ -0,0 +1,113 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Input;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return (($service eq 'input' and $class->applies) or $class->SUPER::provides($service));
}
sub applies {
my $self = shift;
$self->notImplemented(); # this must be overriden by descendants
}
sub defaultOutputProtocol {
my $self = shift;
$self->notImplemented(); # this must be overriden by descendants
}
sub init {
my $self = shift;
my($app) = @_;
$self->SUPER::init(@_);
$self->app($app); # only safe because input services are created as service instances not pure services!!!
$self->fetchArguments();
}
sub next {
return 0;
}
sub fetchArguments {} # stub
# returns the argument, potentially after asking the user or whatever.
sub getArgument {
my $self = shift;
$self->notImplemented();
}
# returns the argument if it has been provided, otherwise undef.
sub peekArgument {
return undef;
}
# XXX I don't like having these here:
sub UA {
my $self = shift;
$self->notImplemented();
}
sub referrer {
my $self = shift;
$self->notImplemented();
}
sub host {
my $self = shift;
$self->notImplemented();
}
sub acceptType {
my $self = shift;
$self->notImplemented();
}
sub acceptCharset {
my $self = shift;
$self->notImplemented();
}
sub acceptEncoding {
my $self = shift;
$self->notImplemented();
}
sub acceptLanguage {
my $self = shift;
$self->notImplemented();
}

View File

@ -0,0 +1,155 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Input::Arguments;
use strict;
use vars qw(@ISA);
use PLIF::Input;
@ISA = qw(PLIF::Input);
1;
sub fetchArguments {
my $self = shift;
$self->splitArguments();
$self->setCommandArgument();
}
# Returns the values given for that argument. In a scalar context,
# returns the first value (or undef if the argument was never
# given). In an array context, returns all the values given.
sub getArgument {
my $self = shift;
my($argument) = @_;
if (not defined($self->{"argument $argument"})) {
$self->createArgument($argument);
}
if (wantarray) {
return @{$self->{"argument $argument"}};
} else {
if (@{$self->{"argument $argument"}}) {
return $self->{"argument $argument"}->[0];
} else {
return undef;
}
}
}
# Returns the values given for that argument if it already exists,
# otherwise undef. In a scalar context, returns the first value (or
# undef if the argument was never given). In an array context, returns
# all the values given.
sub peekArgument {
my $self = shift;
my($argument) = @_;
if (defined($self->{"argument $argument"})) {
if (wantarray) {
return @{$self->{"argument $argument"}};
} elsif (@{$self->{"argument $argument"}}) {
return $self->{"argument $argument"}->[0];
}
}
return undef;
}
# Specifics of this implementation:
sub splitArguments {} # stub
sub addArgument {
my $self = shift;
my($argument, $value) = @_;
if (not defined($self->{"argument $argument"})) {
$self->{"argument $argument"} = [];
}
push(@{$self->{"argument $argument"}}, $value);
}
sub setArgument {
my $self = shift;
my($argument, @value) = @_;
$self->{"argument $argument"} = [@value];
}
# modifies the last value for this argument to the new value
sub pokeArgument {
my $self = shift;
my($argument, $newValue) = @_;
$self->assert(defined($self->{"argument $argument"}), 1, 'Cannot poke an argument that doesn\'t exist yet');
$self->assert(@{$self->{"argument $argument"}} > 0, 1, 'Cannot poke an argument that has no value yet');
$self->{"argument $argument"}->[$#{$self->{"argument $argument"}}] = $newValue;
}
sub resetArguments {
my $self = shift;
foreach my $argument (keys(%{$self})) {
if ($argument =~ /^argument /o) {
delete($self->{$argument});
}
}
}
sub setCommandArgument {
my $self = shift;
my $argument = $self->getArgument('');
if ($argument) {
$self->command($argument);
} else {
$self->command('');
}
}
sub createArgument {
my $self = shift;
my($argument) = @_;
$self->{"argument $argument"} = [];
}
sub propertyExists {
return 1;
}
sub propertyGet {
my $self = shift;
if ($self->SUPER::propertyExists(@_)) {
return $self->SUPER::propertyGet(@_);
} else {
return $self->getArgument(@_); # XXX assumes that return propagates wantarray context...
# if not:
# my @result = $self->getArgument(@_);
# if (wantarray) {
# return @result;
# } else {
# if (@result) {
# return $result[0];
# } else {
# return undef;
# }
# }
}
}

View File

@ -0,0 +1,127 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Input::CGI;
use strict;
use vars qw(@ISA);
use PLIF::Input::Arguments;
use MIME::Base64; # DEPENDENCY
@ISA = qw(PLIF::Input::Arguments);
1;
# Don't forget to put this module ABOVE the "CommandLine" module!
# The CommandLine module can't tell the difference between a keyword
# query and real command line.
sub applies {
return defined($ENV{'GATEWAY_INTERFACE'});
}
sub defaultOutputProtocol {
return 'http';
}
sub splitArguments {
my $self = shift;
foreach my $parameter (qw(SERVER_SOFTWARE SERVER_NAME
GATEWAY_INTERFACE SERVER_PROTOCOL SERVER_PORT REQUEST_METHOD
PATH_INFO PATH_TRANSLATED SCRIPT_NAME QUERY_STRING REMOTE_HOST
REMOTE_ADDR AUTH_TYPE REMOTE_USER REMOTE_IDENT CONTENT_TYPE
CONTENT_LENGTH)) {
$self->propertySet($parameter, $ENV{$parameter});
}
foreach my $parameter (keys(%ENV)) {
if ($parameter =~ /^HTTP_/o) {
$self->propertySet($parameter, $ENV{$parameter});
}
}
if (defined($ENV{'QUERY_STRING'})) {
foreach my $argument (split(/&/o, $ENV{'QUERY_STRING'})) {
if ($argument =~ /^(.*?)(?:=(.*))?$/os) {
$self->addArgument($1, $2);
} else {
$self->warn(2, "argument (|$argument|) did not match regexp (can't happen!)");
}
}
} # should also deal with HTTP POST, PUT, etc, here XXX
if (defined($ENV{'HTTP_AUTHORIZATION'})) {
if ($self->HTTP_AUTHORIZATION =~ /^Basic +(.*)$/os) {
# HTTP Basic Authentication
my($username, $password) = split(/:/, decode_base64($1), 2);
$self->username($username);
$self->password($password);
} else {
# Some other authentication scheme
}
}
}
sub setCommandArgument {
my $self = shift;
my $argument = $self->getArgument('command');
if ($argument) {
$self->command($argument);
} else {
$self->command('');
}
}
sub UA {
my $self = shift;
return $self->getArgument('overrideUserAgent') or $self->HTTP_USER_AGENT;
}
sub referrer {
my $self = shift;
return $self->getArgument('overrideReferrer') or $self->HTTP_REFERER; # (sic)
}
sub host {
my $self = shift;
return $self->REMOTE_HOST or $self->REMOTE_ADDR;
}
sub acceptType {
my $self = shift;
return $self->getArgument('overrideAcceptType') or $self->HTTP_ACCEPT;
}
sub acceptCharset {
my $self = shift;
return $self->getArgument('overrideAcceptCharset') or $self->HTTP_ACCEPT_CHARSET;
}
sub acceptEncoding {
my $self = shift;
return $self->getArgument('overrideAcceptEncoding') or $self->HTTP_ACCEPT_ENCODING;
}
sub acceptLanguage {
my $self = shift;
return $self->getArgument('overrideAcceptLanguage') or $self->HTTP_ACCEPT_LANGUAGE;
}

View File

@ -0,0 +1,130 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Input::CommandLine;
use strict;
use vars qw(@ISA);
use PLIF::Input::Arguments;
use Term::ReadLine; # DEPENDENCY
@ISA = qw(PLIF::Input::Arguments);
1;
sub applies {
return @ARGV > 0;
}
sub defaultOutputProtocol {
return 'stdout';
}
sub splitArguments {
my $self = shift;
# first, make sure the command argument is created even if it is
# not explicitly given -- this avoids us from asking the user what
# command they want in interactive mode.
$self->SUPER::createArgument('');
# next, parse the arguments provided.
my $lastArgument;
foreach my $argument (@ARGV) {
if ($argument =~ /^-([^-]+)$/os) {
my @shortArguments = split(//o, $1);
foreach my $shortArgument (@shortArguments) {
$self->addArgument($shortArgument, 1);
}
$lastArgument = $shortArguments[$#shortArguments];
} elsif ($argument =~ /^--([^-][^=]*)=(.+)$/os) {
$self->addArgument($1, $2);
$lastArgument = undef;
} elsif ($argument =~ /^--no-([^-].+)/os) {
$self->addArgument($1, 0);
$lastArgument = undef;
} elsif ($argument =~ /^--([^-].+)/os) {
$self->addArgument($1, 1);
$lastArgument = $1;
} else {
if (defined($lastArgument)) {
$self->addArgument($lastArgument, $argument);
$lastArgument = undef;
} else {
$self->addArgument('', $argument);
}
}
}
}
sub createArgument {
my $self = shift;
my($argument) = @_;
if ($argument eq 'batch') {
$self->setArgument($argument, 0);
} else {
if ($self->getArgument('batch')) {
$self->SUPER::createArgument($argument);
} else {
$self->dump(5, "going to request $argument from user!");
$self->app->output->request($argument);
# get input from user :-)
my $term = Term::ReadLine->new($self->app->name);
my $value = $term->readline(''); # argument is prompt
# if we cached the input device:
# $term->addhistory($value);
$self->setArgument($argument, $value);
}
}
}
# XXX Grrrr:
sub UA {
return '';
}
sub referrer {
return '';
}
sub host {
return 'localhost';
}
sub acceptType {
return 'text/plain';
}
sub acceptCharset {
return '';
}
sub acceptEncoding {
return '';
}
sub acceptLanguage {
return '';
}

View File

@ -0,0 +1,76 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Input::Default;
use strict;
use vars qw(@ISA);
use PLIF::Input::Arguments;
@ISA = qw(PLIF::Input::Arguments);
1;
sub applies {
return 1;
}
sub defaultOutputProtocol {
return 'stdout';
}
# Everything else that PLIF::Input::Arguments does by default is great
# for this. Namely, no command, and returning 'undef' for everything.
# XXX Grrrr:
sub UA {
return '';
}
sub referrer {
return '';
}
sub host {
return 'localhost';
}
sub acceptType {
return 'text/plain';
}
sub acceptCharset {
return '';
}
sub acceptEncoding {
return '';
}
sub acceptLanguage {
return '';
}

View File

@ -0,0 +1,72 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::MagicPipingArray;
use strict;
use vars qw($AUTOLOAD); # it's a package global
use Carp qw(cluck confess); # stack trace versions of warn and die
1;
# This can be used separate from PLIF, and so does not inherit from
# the PLIF core. Calling any method except 'create' will result in the
# method call being forwarded to the wrapped objects. Calling 'create'
# will create a new MagicPipingArray object, see the AUTOLOAD function
# below for an example.
sub create {
my $class = shift;
if (ref($class)) {
$class = ref($class);
}
my $self = [@_];
bless ($self, $class);
return $self;
}
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/^.*://o; # strip fully-qualified portion
my @allResults;
foreach my $object (@$self) {
my $method = $object->can($name);
if ($method) {
my @result = &$method($object, @_);
if (@result <= 1) {
if (@result and defined($result[0])) {
push(@allResults, @result);
}
} else {
push(@allResults, [@result]);
}
} else {
confess("Failed to find method or property '$name' in object '$object' of MagicPipingArray '$self', aborting"); # die with stack trace
}
}
return $self->create(@allResults);
}

View File

@ -0,0 +1,71 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::MagicSelectingArray;
use strict;
use vars qw($AUTOLOAD); # it's a package global
use Carp qw(cluck confess); # stack trace versions of warn and die
1;
# This can be used separate from PLIF, and so does not inherit from
# the PLIF core. Calling any method except 'create' will result in the
# method call being forwarded to each of the wrapped objects until one
# returns a value (including 'undef'), which will then be returned.
sub create {
my $class = shift;
if (ref($class)) {
$class = ref($class);
}
my $self = [@_];
bless ($self, $class);
return $self;
}
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/^.*://o; # strip fully-qualified portion
my @allResults;
foreach my $object (@$self) {
my $method = $object->can($name);
if ($method) {
my @result = &$method($object, @_);
if (@result) {
if (wantarray) {
return @result;
} else {
return $result[0];
}
}
} else {
confess("Failed to find method or property '$name' in object '$object' of MagicSelectingArray '$self', aborting"); # die with stack trace
}
}
return;
}

View File

@ -0,0 +1,64 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Output;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'output.'.$class->protocol or $class->SUPER::provides($service));
}
sub protocol {
my $self = shift;
$self->notImplemented(); # this must be overriden by descendants
}
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($app) = @_;
$self->propertySet('app', $app);
# output classes disable implied property creation, so we use
# propertySet() here instead of just $self->app($app).
}
sub reportFatalError {} # stub - should this be required? probably...
# disable implied property access so that calls to unimplemented
# output methods will always be caught.
sub propertyImpliedAccessAllowed {
my $self = shift;
$self->dump(10, "access to property @_ of object $self attempted");
return $self->propertyExists(@_);
}

View File

@ -0,0 +1,61 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Output::Generic;
use strict;
use vars qw(@ISA);
use PLIF::Output;
@ISA = qw(PLIF::Output);
1;
sub protocol {
return 'generic';
}
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($app, $session, $protocol) = @_;
$self->propertySet('actualSession', $session);
$self->propertySet('actualProtocol', $protocol);
}
sub output {
my $self = shift;
my($session, $string, $data) = @_;
if (not defined($session)) {
$session = $self->actualSession;
}
my $expander = $self->app->getService("string.expander.$string");
if (not defined($expander)) {
$expander = $self->app->getService('string.expander');
$self->assert($expander, 1, 'Could not find a string expander.');
}
$self->app->getService('output.generic.'.$self->actualProtocol)->output($self->app, $session,
$expander->expand($self->app, $session, $self->actualProtocol, $string, $data));
}

View File

@ -0,0 +1,48 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Output::Generic::StdOut;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'output.generic.http' or
$service eq 'output.generic.stdout' or
$class->SUPER::provides($service));
}
sub output {
my $self = shift;
my($app, $session, $string) = @_;
print $string;
}

View File

@ -0,0 +1,69 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Output::HTTP;
use strict;
use vars qw(@ISA);
use PLIF::Output;
@ISA = qw(PLIF::Output);
1;
sub protocol {
return 'http';
}
sub finaliseHeader {
my $self = shift;
print "Content-Type: " . $self->format . "\n";
foreach my $header ($self->headers) {
print "$header\n";
}
print "\n";
}
sub authenticate {
my $self = shift;
my $realm = $self->realm;
print "HTTP/1.1 401 Unauthorized\nWWW-Authenticate: Basic realm=\"$realm\"\n";
$self->finaliseHeader();
}
sub header {
my $self = shift;
print "HTTP/1.1 200 OK\n";
$self->finaliseHeader();
}
sub realm {
my $self = shift;
$self->notImplemented();
}
sub headers {
return ();
}

View File

@ -0,0 +1,173 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Program;
use strict;
use vars qw(@ISA);
use PLIF::Controller;
@ISA = qw(PLIF::Controller);
# the center of the PLIF-based application:
my $app = 'main'->create();
$app->run();
1;
# setup everything (automatically called by the constructor, above)
sub init {
my $self = shift;
$self->dump(5, '*** Started PLIF Application ***');
$self->SUPER::init(@_);
$self->initInput();
}
# called after the constructor (see above)
# this is the core of the application
sub run {
my $self = shift;
do {
eval {
$self->objects([]);
if ($self->verifyInput()) {
if ($self->input->command) {
$self->dispatch($self->input->command);
} else {
$self->noCommand();
}
} # verifyInput should deal with the errors
};
if ($@) {
$self->dump(3, "previous command didn't go over well: $@");
$self->output->reportFatalError($@);
}
# In case we used a progressive output device, let it shut
# down. It's important to do this, because it holds a
# reference to us and we wouldn't want a memory leak...
$self->defaultOutput(undef);
} while ($self->input->next());
$self->input(undef); # shutdown the input service instance
}
# takes the first applicable input method.
sub initInput {
my $self = shift;
my $input = $self->getServiceInstance('input');
if ($input) {
$self->input($input);
} else {
$self->noInput();
}
}
# Returns an applicable output method. If you need a particular
# protocol, pass it as a parameter. To get the default output class
# given the current objects, do not pass any parameters. The output
# object is a one-off and is not (and should not) be cached; once you
# have called the relevant output method on it let it go out of scope
# and that should be it.
# You may also pass a session argument (typically the object
# representing a user, for example). If you don't pass any, a the
# first session object that was created by the input verifiers is used
# instead (e.g. during authentication).
sub output {
my $self = shift;
my($protocol, $session) = @_;
my $default = 0;
if (not $protocol) {
if (defined($self->defaultOutput)) {
return $self->defaultOutput;
}
if ($session) {
$self->warn(3, 'Tried to use default output method for a specific session object');
$session = undef;
}
$default = 1;
$protocol = $self->selectOutputProtocol();
}
my $output = $self->getServiceInstance("output.$protocol", $session);
if (not $output) {
$output = $self->getServiceInstance("output.generic", $session, $protocol);
if (not $output) {
$self->error(0, 'Could not find an applicable output class');
}
}
if ($default) {
$self->defaultOutput($output);
}
return $output;
}
sub verifyInput {
my $self = shift;
# we invoke all the input verifiers until one fails
my($result) = $self->getSelectingServiceList('input.verify')->verifyInput($self);
if (defined($result)) {
# if one failed, then the result will be the object that should report the error
$result->reportInputVerificationError($self);
return 0;
} else {
return 1;
}
}
sub selectOutputProtocol {
my $self = shift;
return $self->input->defaultOutputProtocol;
}
# Implementation Specific Methods
# At least some of these should be overriden by real applications
# if you override this one, only call $self->SUPER::dispatch(@_) if
# you couldn't dispatch the command.
sub dispatch {
my $self = shift;
my($command) = @_;
my $method = $self->can('cmd'.$command);
if ($method) {
&$method($self);
} else {
$self->unknownCommand();
}
}
sub noInput {
my $self = shift;
$self->error(0, 'Could not find an applicable input method');
}
sub unknownCommand {
my $self = shift;
$self->error(0, 'The command given was not recognised.');
}
sub noCommand {
my $self = shift;
$self->unknownCommand(@_);
}

View File

@ -0,0 +1,135 @@
PLIF: Program Logic Insulation Framework
========================================
Overview
--------
PLIF is based around the idea of services. Some more text really
should go here, don't you think?
Note that in this document, the term "provide a service" is used to
mean what some people term "implement an interface". The terms are
interchangeable. Due to Perl's amorphous nature, I found the term
"interface" to be a bit inappropriate, in my opinion it has
connotations of strictness that are not really applicable to Perl...
Services
--------
The following services (also termed 'interfaces') have meaning in the
basic PLIF design:
input - implements all of the Input API
input.verify - provides a verifyInput() method
output.<protocol> - implements all of the Output API
dataSource.<dataSet> - provides a data source for dataSet
dataSource.setupAware - knows how to set up the database
input semantics
---------------
These are not simple. See the example implementations.
input.verify semantics
----------------------
If you provide input.verify, then you should expect to get called each
time a set of input needs validating. Input validation means stuff
like checking that an e-mail's format is correct, or authentication
the user if they have tried to log in, or whatever.
When your "verifyInput()" method is called, you should return nothing
(not even undef) if everything is ok, and a reference to an object
that has a reportInputVerificationError() method if something went
wrong. If you return yourself (i.e., if you implement both methods
being discussed here), remember that there is no guarentee that you
will be destructed before the next time you are called, so don't
design your verifyInput() method in a way that assumes you won't have
pre-existing state. Also, since there is no guarentee that your
reportInputVerificationError() method will be called, don't hold on to
any references like, say, input. If you do you might end up leaking
memory, which we don't want!
Verifiers will be called in the order they were registered. If any
fail (i.e., return an object) then the verification loop is aborted.
For this reason, you should register verifiers in the order that they
are most likely to fail so that the loop ends in the shortest time.
If successful, the verifiers should initialise any objects (like user
objects from successful authentication) using $app->addObject(). The
first object claiming to provide the 'session' service is the object
that will be passed (by default) to output services.
output.* semantics
------------------
Output uses the following kind of command flow through services:
logic
|
+---------+---------+
| |
output.<protocol> output.generic
|
+------------+------------+
| |
string.expander.<string> string.expander
| |
+------------+------------+
|
output.generic.<protocol>
string.expander semantics
-------------------------
These services provide and |expand| function that takes four
arguments: the application object, the name of the protocol, the name
of the string to expand, and the data hash. The function should return
an opaque string.
dataSource.setupAware
---------------------
Most data sources are expected to provide the "dataSource.setupAware"
service, which basically means that they have a setup method that does
whatever is required to the databases. We should also introduce some
uninstall awareness, some default data populatingness, and stuff...
Class Tree
----------
CORE
|
+------+------------+-------------------+
| | | |
PLIF ... MagicPipingArray MagicSelectingArray
|
+---------+-----------------------+--------------------+
Controller | |
| Service DBI
Program | (ResultsFrame)
| |
Application +--------+-------+-----+-----+-------+-------+--------+
| | | | | | | |
... DataSource COSES DataBase Input Object Output StdOut
| | | | | (Outputter)
+--------+------+----+ +--+--+ Arguments ... +--+--+
| | | | | | | |
... Configuration Strings CfgFile DBI +-----+-----+ ... Generic
| | | |
MySQL CommandLine CGI Default
Contributions
-------------
Please make sure you read the STYLEGUIDE if you want to consider
writing code.

View File

@ -0,0 +1,36 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Service;
use strict;
use vars qw(@ISA);
use PLIF;
@ISA = qw(PLIF);
1;
sub provides { return 0; } # stub

View File

@ -0,0 +1,74 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Service::AdminCommands;
use strict;
use vars qw(@ISA);
use PLIF::Service::Dispatcher;
@ISA = qw(PLIF::Service::Dispatcher);
1;
# Any application that uses PLIF::Service::AdminCommands must also
# have an output implementation that supports "setupFailed($result)"
# and "setupSucceeded()".
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'input.verify' or $class->SUPER::provides($service));
}
sub verifyInput {
my $self = shift;
my($app) = @_;
if ($app->input->isa('PLIF::Input::CommandLine')) {
$app->addObject($self);
}
return;
}
sub cmdSetup {
my $self = shift;
my($app) = @_;
my $result;
$result = $app->getSelectingServiceList('setup.configure')->setupConfigure($app);
if (not $result) {
$result = $app->getSelectingServiceList('setup.install')->setupInstall($app);
}
if ($result) {
$app->output->setupFailed($result);
} else {
$app->output->setupSucceeded();
}
}
# XXX other commands to add
# cmdAddModule
# cmdRemoveModule

View File

@ -0,0 +1,382 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Service::Coses;
use strict;
use vars qw(@ISA);
use PLIF::Service;
use XML::Parser; # DEPENDENCY
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'string.expander' or $service eq 'service.coses' or $class->SUPER::provides($service));
}
sub expand {
my $self = shift;
my($app, $session, $protocol, $string, $data) = @_;
my @index = (); my $index = 0;
my @stack = (); my $stack = $self->parseString($self->getString($app, $session, $protocol, $string));
my @scope = (); my $scope = {'data' => $data};
my $result = '';
node: while (1) {
if ($index > $#$stack) {
# end of this level, pop the stack
if (@stack) {
$stack = pop(@stack);
$index = pop(@index);
$scope = pop(@scope);
} else {
# end of stack, have a nice day!
return $result;
}
} else {
# more data to deal with at this level
my $node = $stack->[$index];
my $contents = $stack->[$index+1];
my $superscope = $scope; # scope of parent element
$index += 2; # move the pointer on to the next node
if ($node) {
# element node
my $attributes = $contents->[0];
if ($attributes->{'xml:space'}) {
$scope = {%$scope}; # take a local copy of the root level for descendants
$scope->{'coses: white space'} = $attributes->{'xml:space'} eq 'default'; # vs 'preserve', which is assumed
}
if ($node eq 'if') {
if (not $self->evaluateCondition($self->evaluateExpression($attributes->{'lvalue'}, $scope),
$self->evaluateExpression($attributes->{'rvalue'}, $scope),
$self->evaluateExpression($attributes->{'condition'}, $scope),
)) {
$superscope->{'coses: last condition'} = 0;
next node;
}
$superscope->{'coses: last condition'} = 1;
if ($scope == $superscope) {
$scope = {%$scope};
}
$scope->{'coses: last condition'} = 0;
} elsif ($node eq 'set') {
my $variable = $self->evaluateExpression($attributes->{'variable'}, $scope);
$self->assert($variable !~ /[\(\.\)]/o, 1,
"variable '$variable' contains one of '(', ')' or '.' and is therefore not valid to use as a variable name.");
my $value = $self->evaluateExpression($attributes->{'value'}, $scope);
my $order = $self->evaluateExpression($attributes->{'order'}, $scope);
my $source = $self->evaluateExpression($attributes->{'source'}, $scope);
if ($order or $source) {
my @items = $self->sort($order, $self->keys($value, $source));
push(@index, $index);
push(@stack, $stack);
push(@scope, $superscope);
# now we push all but one of the items onto
# the stack -- so first take that item...
my $firstItem = pop(@items); # (@items is sorted backwards)
# and then take a copy of the scope if we didn't already
if ($scope == $superscope) {
$scope = {%$scope};
}
foreach my $item (@items) {
push(@index, 1);
push(@stack, $contents);
$scope->{$variable} = $item;
push(@scope, $scope);
# make sure we create a new scope for the
# next item -- otherwise each part of the
# loop would just have a reference to the
# same shared hash, and so they would all
# have the same value!
$scope = {%$scope};
}
# and finally create the first scope (not pushed on the stack, it is the next, live one)
$index = 1; # skip past attributes
$stack = $contents;
$scope->{$variable} = $firstItem;
next node;
} else {
if ($scope == $superscope) {
# take a copy since we haven't yet
$scope = {%$scope};
}
$scope->{$variable} = $value;
}
} elsif ($node eq 'text') {
if ($attributes->{'value'}) {
$result .= $self->evaluateExpression($attributes->{'value'}, $scope);
next node; # skip contents if attribute 'value' is present
}
} elsif ($node eq 'br') {
# useful if xml:space is set to 'default'
$result .= "\n";
} elsif ($node eq 'embed') {
push(@index, $index);
push(@stack, $stack);
$index = 0;
$stack = $self->parseString($self->getString($app, $session, $protocol, $self->evaluateExpression($attributes->{'string'}, $scope)));
push(@scope, $superscope);
next node; # skip default handling
} elsif ($node eq 'else') {
if ($superscope->{'coses: last condition'}) {
next node; # skip this block if the variable IS there
}
} elsif ($node eq 'with') {
my $variable = $self->evaluateExpression($attributes->{'variable'}, $scope);
if (not defined($scope->{$variable})) {
next node; # skip this block if the variable isn't there
}
} elsif ($node eq 'without') {
my $variable = $self->evaluateExpression($attributes->{'variable'}, $scope);
if (defined($scope->{$variable})) {
next node; # skip this block if the variable IS there
}
} else {
$self->error(1, "Tried to expand a string with an unrecognised COSES element: '$node'");
}
# fall through to default handling: push current
# stack, scope and index, and set new index to move
# past attributes
push(@index, $index); $index = 1;
push(@stack, $stack); $stack = $contents;
push(@scope, $superscope);
} elsif ($scope->{'coses: white space'}) {
# raw text node which may or may not be included
if ($contents =~ /\S/o) {
# if xml:space="default" then only include text nodes with non-whitespace.
$result .= $contents;
}
} else {
# raw text node
$result .= $contents;
}
}
}
}
sub parseString {
my $self = shift;
my($string) = @_;
# parse string (it's an XML file)
my $parser = new XML::Parser(Style => 'Tree');
return $parser->parse($string);
}
sub getString {
my $self = shift;
my($app, $session, $protocol, $string) = @_;
return $app->getService('dataSource.strings')->get($app, $session, $protocol, $string);
}
sub evaluateVariable {
my $self = shift;
my($variable, $scope) = @_;
my @parts = split(/\./o, $variable); # split variable at dots ('.')
# drill down through scope
foreach my $part (@parts) {
if (ref($scope) eq 'HASH') {
$scope = $scope->{$part};
} elsif (ref($scope) eq 'ARRAY') {
$scope = $scope->[$part];
} else {
$self->error(1, "Could not resolve '$variable' at '$part'");
}
}
if (defined($scope)) {
# fully dereference all scalar references
while (ref($scope) eq 'SCALAR') {
$scope = $$scope;
}
return $scope;
} else {
return '';
}
}
sub evaluateNestedVariableSafely {
my $self = shift;
my($variable, $scope) = @_;
$scope = $self->evaluateVariable($variable, $scope);
if ($scope =~ /[\(\)]/o) {
$self->error(1, "Evaluated nested variable '$variable' to '$scope' which contains one of '(', or ')' and is therefore not safe to use as a variable part");
}
return $scope;
}
sub evaluateExpression {
my $self = shift;
my($expression, $scope) = @_;
if (defined($expression)) {
if ($expression =~ /^\'(.*)$/os) {
return $1; # bypass next bit if it's an explicit string
} elsif ($expression =~ /^[^()]*$/o) {
return $expression; # bypass next bit if there are no brackets at all
} else {
# expand *nested* variables safely
while ($expression =~ s/^ # the start of the line
( # followed by a group of
.*\( # anything up to an open bracket
[^()]* # then anything but brackets
) # followed by
\( # an open bracket
([^()]*) # our variable
\) # a close bracket
( # followed by a group of
(?: # as many instances as required
[^()]* # of first other-variable stuff
\([^()]*\) # and then of more embedded variabled
)* # followed by
[^()]*\).* # anything but brackets, a close bracket then anything
) # which should be at the
$ # end of the line
/$1.$self->evaluateNestedVariableSafely($2, $scope).$3/sexo) {
# this should cope with this smoketest (d=ab, g=fcde): (f.(c).((a).(b)).(e))
}
# expand outer variable without safety checks, if there are any
# first, check if the result would be a single variable
if ($expression =~ /^\(([^()]*)\)$/o) {
# we special case this -- doing it without using a
# regexp s/// construct ensures we keep references as
# live references in strict mode (i.e., we don't call
# their "ToString" method or whatever...)
$expression = $self->evaluateVariable($1, $scope);
} else {
# expand all remaining outer variables
my $result = '';
while ($expression =~ s/^(.*?)\(([^()]*)\)//o) {
# ok, let's deal with the next embedded variable
$result .= $1.$self->evaluateVariable($2, $scope);
# the bit we've dealt with so far will end up
# removed from the $expression string (so the
# current state is "$result$expression). This is
# so that things that appear to be variables in
# the strings we are expanding don't themselves
# get expanded.
}
# put it back together again
$expression = $result.$expression;
}
# and return the result
return $expression;
}
} else {
return '';
}
}
sub evaluateCondition {
my $self = shift;
my($lvalue, $rvalue, $condition) = @_;
if (defined($condition) and defined($lvalue) and defined($rvalue)) {
if ($condition eq '=') {
return $lvalue == $rvalue;
} elsif ($condition eq '!=') {
return $lvalue != $rvalue;
} elsif ($condition eq '<') {
return $lvalue < $rvalue;
} elsif ($condition eq '>') {
return $lvalue > $rvalue;
} elsif ($condition eq '<=') {
return $lvalue <= $rvalue;
} elsif ($condition eq '>=') {
return $lvalue >= $rvalue;
} elsif ($condition eq 'eq') {
return $lvalue eq $rvalue;
} elsif ($condition eq 'ne') {
return $lvalue ne $rvalue;
} elsif ($condition eq '=~') {
return eval { $lvalue =~ /$rvalue/; }; # XXX does this actually work? ;-)
} elsif ($condition eq '!~') {
return eval { $lvalue !~ /$rvalue/; };
} elsif ($condition eq 'is') {
if (ref($lvalue)) {
return $rvalue eq lc(ref($lvalue));
} else {
return $rvalue eq 'scalar';
}
} elsif ($condition eq 'is not') {
if (ref($lvalue)) {
return $rvalue ne lc(ref($lvalue));
} else {
return $rvalue ne 'scalar';
}
}
} # else, well, they got it wrong, so it won't match now will it :-)
return 0;
}
sub keys {
my $self = shift;
my($value, $source) = @_;
if (ref($value) eq 'HASH') {
if (defined($source) and $source eq 'values') {
return values(%$value);
} else { # (not defined($source) or $source eq 'keys')
return keys(%$value);
}
} elsif (ref($value) eq 'ARRAY') {
if (defined($source) and $source eq 'values') {
return @$value;
} else { # (not defined($source) or $source eq 'keys')
if ($#$value >= 0) {
return (0..$#$value);
} else {
return ();
}
}
} else {
return ($value);
}
}
sub sort {
my $self = shift;
my($order, @list) = @_;
# sort the list (in reverse order!)
if (defined($order)) {
if ($order eq 'lexical') {
return sort { $b cmp $a } @list;
} elsif ($order eq 'reverse lexical') {
return sort { $a cmp $b } @list;
} elsif ($order eq 'case insensitive lexical') {
return sort { lc($b) cmp lc($a) } @list;
} elsif ($order eq 'reverse case insensitive lexical') {
return sort { lc($a) cmp lc($b) } @list;
} elsif ($order eq 'numerical') {
return sort { $b <=> $a } @list;
} elsif ($order eq 'reverse numerical') {
return sort { $a <=> $b } @list;
} elsif ($order eq 'length') {
return sort { length($b) <=> length($b) } @list;
} elsif ($order eq 'reverse length') {
return sort { length($a) <=> length($a) } @list;
}
}
# else:
return reverse @list;
}

View File

@ -0,0 +1,49 @@
Sample COSES files
==================
<!--
!
! This example will dump every single string passed into it. For
! example, if you pass it a hash with one item 'data' containing two
! items 'a' and 'b' with 'a' containing 'hello' and 'b' containing
! an array of two values 'wonderful' and 'world', you would get as
! output the following:
!
! data.a = hello
! data.b.1 = wonderful
! data.b.2 = world
!
! This example uses almost all the features of COSES, and so is
! quite a useful example to study. (It doesn't use <else/> or all
! the values of <set>'s attributes.) It's also a great help when
! debugging! You can use it at any point in a COSES document merely
! by nesting it, so you can, for example, study what is happening
! with a <set> statement. If you declare this example as having the
! name 'debug.dumpVars' then to embed it you would do:
!
! <embed string="debug.dumpVars"/>
!
! This example is covered by the same license terms as COSES itself.
! Author: Ian Hickson
!
!-->
<text xml:space="default"> <!-- trim whitespace -->
<with variable="prefix">
<if lvalue="((prefix))" condition="is" rvalue="scalar">
<text value=" (prefix)"/> = <text value="((prefix))"/><br/>
</if>
<if lvalue="((prefix))" condition="is not" rvalue="scalar">
<set variable="index" value="((prefix))" source="keys" order="case insensitive lexical">
<set variable="prefix" value="(prefix).(index)">
<embed string="debug.dumpVars"/>
</set>
</set>
</if>
</with>
<without variable="prefix">
<set variable="prefix" value="()" source="keys" order="lexical">
<embed string="debug.dumpVars"/>
</set>
</without>
</text>

View File

@ -0,0 +1,52 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Service::Dispatcher;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'commands.dispatcher' or $class->SUPER::provides($service));
}
sub dispatch {
my $self = shift;
my($app, $command) = @_;
my $method = $self->can('cmd'.$command);
if ($method) {
&$method($self, $app);
return 1;
} else {
return undef;
}
}

View File

@ -0,0 +1,47 @@
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Service::Session;
use strict;
use vars qw(@ISA);
use PLIF::Service;
@ISA = qw(PLIF::Service);
1;
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'session' or $class->SUPER::provides($service));
}
# expected by dataSource.strings
sub selectVariant {
my $self = shift;
my($app, $protocol) = @_;
return undef; # 'use some other method to work it out...'
}

View File

@ -0,0 +1,127 @@
Coding Style for PLIF
---------------------
This style guide is designed to make sure that the code is consistent
throughout. It isn't necessarily the best way of writing Perl, but it
is consistent. Consistency is more important than using your preferred
method. Please follow the style guide.
Note: Exceptions will be accepted if they improve performance, but
only if they are well commented.
1. Brackets are preferred to other punctuation
return ($a eq 'a' or $b); # preferred
return $a eq 'a' || $b;
2. Use brackets around all function arguments
push(@list, $item); # preferred
push @list, $item;
foreach $item (sort(keys(%{$self->list}))) { } # preferred
foreach $item (sort keys %{$self->list}) { }
3. When calling a method for its side-effect, always use brackets
$self->go(); # preferred
$self->go;
4. When calling a method as if it was a property, omit brackets
return $self->name; # preferred;
return $self->name();
5. Don't use print(), use dump()
$self->dump(9, "foo called with bar $bar"); # preferred
print("foo called with bar $bar\n");
6. To set a property, use the method call notation
$self->name('foo'); # preferred
$self->{'name'} = 'foo';
7. method and property names should start lowercase and have a capital
letter for each word
sub myLovelyMethod { ... } # preferred
sub MyLovelyMethod { ... } # bad (first letter not lowercase)
sub mylovelymethod { ... } # bad (intervening words not capitalized)
sub my_lovely_method { ... } # bad (underscores)
8. methods should start with setting $self and taking their arguments
sub myLovelyMethod {
my $self = shift;
my($argument) = @_;
# code...
}
9. Curly brackets should cuddle
if ($condition) {
# do something
} else {
# do something else
}
10. Comments should be indented just like code
if ($condition) {
# preferred
} else {
# bad
}
11. Avoid using the implicit $_ variable
foreach my $item (@list) { $item++; } # preferred
foreach (@list) { $_++; }
12. Thou shalt avoid using useful functions (which break Win32):
alarm, chroot, crypt, endgrent, endhostent, endnetent,
endprotoent, endpwent, endservent, fork, getgrent, getgrgid,
getgrnam, getnetbyaddr, getnetbyname, getnetent, getpgrp,
getppid, getpriority, getprotoent, getpwent, getpwnam, getpwuid,
getservent, link, msgctl, msgget, msgrcv, msgsnd, semctl, semget,
semop, setgrent, sethostent, setnetent, setpgrp, setpriority,
setprotoent, setpwent, setservent, shmctl, shmget, shmread,
shmwrite, socketpair, symlink, syscall
http://ftp.univie.ac.at/packages/perl/ports/nt/FAQ/perlwin32faq5.html
13. When creating a new dependency, make sure you mark it with the
magic string 'DEPENDENCY', as in:
package PLIF::Coses;
use strict;
use vars qw(@ISA);
use PLIF::Service;
use XML::Parser; # DEPENDENCY
@ISA = qw(PLIF::Service);
1;
This allows for an easy listing of each dependency using 'find'
and 'grep'.
14. The order for declaring methods should be something along the
lines of first class methods, then the constructor (in PLIF this
is 'init'), then the methods you are overriding, then the new
methods, then the destructor ('DESTROY'). This isn't cast in stone
though. Whatever works best.