aboutsummaryrefslogtreecommitdiffstats
path: root/lib/Reaction/UI/ViewPort/Field
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Reaction/UI/ViewPort/Field')
-rw-r--r--lib/Reaction/UI/ViewPort/Field/Boolean.pm32
-rw-r--r--lib/Reaction/UI/ViewPort/Field/ChooseMany.pm139
-rw-r--r--lib/Reaction/UI/ViewPort/Field/ChooseOne.pm138
-rw-r--r--lib/Reaction/UI/ViewPort/Field/DateTime.pm89
-rw-r--r--lib/Reaction/UI/ViewPort/Field/File.pm45
-rw-r--r--lib/Reaction/UI/ViewPort/Field/HiddenArray.pm42
-rw-r--r--lib/Reaction/UI/ViewPort/Field/Number.pm31
-rw-r--r--lib/Reaction/UI/ViewPort/Field/Password.pm32
-rw-r--r--lib/Reaction/UI/ViewPort/Field/String.pm34
-rw-r--r--lib/Reaction/UI/ViewPort/Field/Text.pm32
-rw-r--r--lib/Reaction/UI/ViewPort/Field/TimeRange.pm151
11 files changed, 765 insertions, 0 deletions
diff --git a/lib/Reaction/UI/ViewPort/Field/Boolean.pm b/lib/Reaction/UI/ViewPort/Field/Boolean.pm
new file mode 100644
index 0000000..34f7aae
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/Boolean.pm
@@ -0,0 +1,32 @@
+package Reaction::UI::ViewPort::Field::Boolean;
+
+use Reaction::Class;
+
+class Boolean is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'Bool');
+ has '+layout' => (default => 'checkbox');
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::Boolean
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/ChooseMany.pm b/lib/Reaction/UI/ViewPort/Field/ChooseMany.pm
new file mode 100644
index 0000000..0ea4ed0
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/ChooseMany.pm
@@ -0,0 +1,139 @@
+package Reaction::UI::ViewPort::Field::ChooseMany;
+
+use Reaction::Class;
+
+class ChooseMany is 'Reaction::UI::ViewPort::Field::ChooseOne', which {
+
+ has '+layout' => (default => 'dual_select_group');
+
+ has '+value' => (isa => 'ArrayRef');
+
+ has available_value_names =>
+ (isa => 'ArrayRef', is => 'ro', lazy_build => 1);
+
+ has value_names => (isa => 'ArrayRef', is => 'ro', lazy_build => 1);
+
+ my $listify = sub { # quick utility function, $listify->($arg)
+ return (defined($_[0])
+ ? (ref($_[0]) eq 'ARRAY'
+ ? $_[0] # \@arr => \@arr
+ : [$_[0]]) # $scalar => [$scalar]
+ : []); # undef => []
+ };
+
+ around value => sub {
+ my $orig = shift;
+ my $self = shift;
+ if (@_) {
+ my $value = $listify->(shift);
+ if (defined $value) {
+ $_ = $self->str_to_ident($_) for @$value;
+ my $checked = $self->attribute->check_valid_value($self->action, $value);
+ # i.e. fail if any of the values fail
+ confess "Not a valid set of values"
+ if (@$checked < @$value || grep { !defined($_) } @$checked);
+
+ $value = $checked;
+ }
+ $orig->($self, $value);
+ } else {
+ $orig->($self);
+ }
+ };
+
+ override build_value => sub {
+ return super() || [];
+ };
+
+ implements is_current_value => as {
+ my ($self, $check_value) = @_;
+ my @our_values = @{$self->value||[]};
+ #$check_value = $check_value->id if ref($check_value);
+ #return grep { $_->id eq $check_value } @our_values;
+ $check_value = $self->obj_to_str($check_value) if ref($check_value);
+ return grep { $self->obj_to_str($_) eq $check_value } @our_values;
+ };
+
+ implements current_values => as {
+ my $self = shift;
+ my @all = grep { $self->is_current_value($_) } @{$self->valid_values};
+ return [ @all ];
+ };
+
+ implements available_values => as {
+ my $self = shift;
+ my @all = grep { !$self->is_current_value($_) } @{$self->valid_values};
+ return [ @all ];
+ };
+
+ implements build_available_value_names => as {
+ my $self = shift;
+ my @all = @{$self->available_values};
+ my $meth = $self->value_map_method;
+ my @names = map { $_->$meth } @all;
+ return [ sort @names ];
+ };
+
+ implements build_value_names => as {
+ my $self = shift;
+ my @all = @{$self->value||[]};
+ my $meth = $self->value_map_method;
+ my @names = map { $_->$meth } @all;
+ return [ sort @names ];
+ };
+
+ around handle_events => sub {
+ my $orig = shift;
+ my ($self, $events) = @_;
+ my $ev_value = $listify->($events->{value});
+ if (delete $events->{add_all_values}) {
+ $events->{value} = $self->valid_values;
+ }
+ if (delete $events->{do_add_values} && exists $events->{add_values}) {
+ my $add = $listify->(delete $events->{add_values});
+ $events->{value} = [ @{$ev_value}, @$add ];
+ }
+ if (delete $events->{remove_all_values}) {
+ $events->{value} = [];
+ }
+ if (delete $events->{do_remove_values} && exists $events->{remove_values}) {
+ my $remove = $listify->(delete $events->{remove_values});
+ my %r = map { ($_ => 1) } @$remove;
+ $events->{value} = [ grep { !$r{$_} } @{$ev_value} ];
+ }
+ return $orig->(@_);
+ };
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::ChooseMany
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 is_current_value
+
+=head2 current_values
+
+=head2 available_values
+
+=head2 available_value_names
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/ChooseOne.pm b/lib/Reaction/UI/ViewPort/Field/ChooseOne.pm
new file mode 100644
index 0000000..ea0db1d
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/ChooseOne.pm
@@ -0,0 +1,138 @@
+package Reaction::UI::ViewPort::Field::ChooseOne;
+
+use Reaction::Class;
+use URI;
+use Scalar::Util 'blessed';
+
+class ChooseOne is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+layout' => (default => 'select');
+
+ has valid_value_names => (isa => 'ArrayRef', is => 'ro', lazy_build => 1);
+
+ has valid_values => (isa => 'ArrayRef', is => 'ro', lazy_build => 1);
+
+ has name_to_value_map => (isa => 'HashRef', is => 'ro', lazy_build => 1);
+
+ has value_to_name_map => (isa => 'HashRef', is => 'ro', lazy_build => 1);
+
+ has value_map_method => (
+ isa => 'Str', is => 'ro', required => 1, default => sub { 'display_name' },
+ );
+
+ around value => sub {
+ my $orig = shift;
+ my $self = shift;
+ if (@_) {
+ my $value = shift;
+ if (defined $value) {
+ if (!ref $value) {
+ $value = $self->str_to_ident($value);
+ }
+ my $checked = $self->attribute->check_valid_value($self->action, $value);
+ confess "${value} is not a valid value" unless defined($checked);
+ $value = $checked;
+ }
+ $orig->($self, $value);
+ } else {
+ $orig->($self);
+ }
+ };
+
+ implements build_valid_values => as {
+ my $self = shift;
+ return [ $self->attribute->all_valid_values($self->action) ];
+ };
+
+ implements build_valid_value_names => as {
+ my $self = shift;
+ my $all = $self->valid_values;
+ my $meth = $self->value_map_method;
+ my @names = map { $_->$meth } @$all;
+ return [ sort @names ];
+ };
+
+ implements build_name_to_value_map => as {
+ my $self = shift;
+ my $all = $self->valid_values;
+ my $meth = $self->value_map_method;
+ my %map;
+ $map{$_->$meth} = $self->obj_to_str($_) for @$all;
+ return \%map;
+ };
+
+ implements build_value_to_name_map => as {
+ my $self = shift;
+ my $all = $self->valid_values;
+ my $meth = $self->value_map_method;
+ my %map;
+ $map{$self->obj_to_str($_)} = $_->$meth for @$all;
+ return \%map;
+ };
+
+ implements is_current_value => as {
+ my ($self, $check_value) = @_;
+ my $our_value = $self->value;
+ return unless ref($our_value);
+ $check_value = $self->obj_to_str($check_value) if ref($check_value);
+ return $self->obj_to_str($our_value) eq $check_value;
+ };
+
+ implements str_to_ident => as {
+ my ($self, $str) = @_;
+ my $u = URI->new('','http');
+ $u->query($str);
+ return { $u->query_form };
+ };
+
+ implements obj_to_str => as {
+ my ($self, $obj) = @_;
+ return $obj unless ref($obj);
+ confess "${obj} not an object" unless blessed($obj);
+ my $ident = $obj->ident_condition;
+ my $u = URI->new('', 'http');
+ $u->query_form(%$ident);
+ return $u->query;
+ };
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::ChooseOne
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 is_current_value
+
+=head2 value
+
+=head2 valid_values
+
+=head2 valid_value_names
+
+=head2 value_to_name_map
+
+=head2 name_to_value_map
+
+=head2 str_to_ident
+
+=head2 obj_to_str
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/DateTime.pm b/lib/Reaction/UI/ViewPort/Field/DateTime.pm
new file mode 100644
index 0000000..2b8509f
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/DateTime.pm
@@ -0,0 +1,89 @@
+package Reaction::UI::ViewPort::Field::DateTime;
+
+use Reaction::Class;
+use Reaction::Types::DateTime;
+use Time::ParseDate ();
+
+class DateTime is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'DateTime');
+
+ has '+layout' => (default => 'dt_textfield');
+
+ has value_string => (
+ isa => 'Str', is => 'rw', lazy_build => 1,
+ trigger_adopt('value_string')
+ );
+
+ has value_string_default_format => (
+ isa => 'Str', is => 'rw', required => 1, default => sub { "%F %H:%M:%S" }
+ );
+
+ implements build_value_string => as {
+ my $self = shift;
+
+ # XXX
+ #<mst> aha, I know why the fucker's lazy
+ #<mst> it's because if value's calculated
+ #<mst> it needs to be possible to clear it
+ #<mst> eval { $self->value } ... is probably the best solution atm
+ my $value = eval { $self->value };
+ return '' unless $self->has_value;
+ my $format = $self->value_string_default_format;
+ return $value->strftime($format) if $value;
+ return '';
+ };
+
+ implements adopt_value_string => as {
+ my ($self) = @_;
+ my $value = $self->value_string;
+ my ($epoch) = Time::ParseDate::parsedate($value, UK => 1);
+ if (defined $epoch) {
+ my $dt = 'DateTime'->from_epoch( epoch => $epoch );
+ $self->value($dt);
+ } else {
+ $self->message("Could not parse date or time");
+ $self->clear_value;
+ $self->needs_sync(1);
+ }
+ };
+
+ override accept_events => sub {
+ ('value_string', super());
+ };
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::DateTime
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 value_string
+
+Accessor for the string representation of the DateTime object.
+
+=head2 value_string_default_format
+
+By default it is set to "%F %H:%M:%S".
+
+=head1 SEE ALSO
+
+=head2 L<DateTime>
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/File.pm b/lib/Reaction/UI/ViewPort/Field/File.pm
new file mode 100644
index 0000000..557826d
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/File.pm
@@ -0,0 +1,45 @@
+package Reaction::UI::ViewPort::Field::File;
+
+use Reaction::Class;
+use Reaction::Types::File;
+
+class File is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'File', required => 0);
+
+ has '+layout' => (default => 'file');
+
+ override apply_our_events => sub {
+ my ($self, $ctx, $events) = @_;
+ my $value_key = join(':', $self->location, 'value');
+ if (my $upload = $ctx->req->upload($value_key)) {
+ local $events->{$value_key} = $upload;
+ return super();
+ } else {
+ return super();
+ }
+ };
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::File
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/HiddenArray.pm b/lib/Reaction/UI/ViewPort/Field/HiddenArray.pm
new file mode 100644
index 0000000..7f8cc73
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/HiddenArray.pm
@@ -0,0 +1,42 @@
+package Reaction::UI::ViewPort::Field::HiddenArray;
+
+use Reaction::Class;
+
+class HiddenArray is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'ArrayRef');
+
+ around value => sub {
+ my $orig = shift;
+ my $self = shift;
+ if (@_) {
+ $orig->($self, (ref $_[0] eq 'ARRAY' ? $_[0] : [ $_[0] ]));
+ $self->sync_to_action;
+ } else {
+ $orig->($self);
+ }
+ };
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::HiddenArray
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/Number.pm b/lib/Reaction/UI/ViewPort/Field/Number.pm
new file mode 100644
index 0000000..e4e925f
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/Number.pm
@@ -0,0 +1,31 @@
+package Reaction::UI::ViewPort::Field::Number;
+
+use Reaction::Class;
+
+class Number is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+layout' => (default => 'textfield');
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::Number
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/Password.pm b/lib/Reaction/UI/ViewPort/Field/Password.pm
new file mode 100644
index 0000000..d70ed62
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/Password.pm
@@ -0,0 +1,32 @@
+package Reaction::UI::ViewPort::Field::Password;
+
+use Reaction::Class;
+
+class Password is 'Reaction::UI::ViewPort::Field::String', which {
+
+ has '+value' => (isa => 'SimpleStr');
+ has '+layout' => (default => 'password');
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::Password
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/String.pm b/lib/Reaction/UI/ViewPort/Field/String.pm
new file mode 100644
index 0000000..4be6bdc
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/String.pm
@@ -0,0 +1,34 @@
+package Reaction::UI::ViewPort::Field::String;
+
+use Reaction::Class;
+
+class String is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'Str'); # accept over 255 chars in case, upstream
+ # constraint from model should catch it
+
+ has '+layout' => (default => 'textfield');
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::String
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/Text.pm b/lib/Reaction/UI/ViewPort/Field/Text.pm
new file mode 100644
index 0000000..d4e89f8
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/Text.pm
@@ -0,0 +1,32 @@
+package Reaction::UI::ViewPort::Field::Text;
+
+use Reaction::Class;
+
+class Text is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'Str');
+ has '+layout' => (default => 'textarea');
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::Text
+
+=head1 DESCRIPTION
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
diff --git a/lib/Reaction/UI/ViewPort/Field/TimeRange.pm b/lib/Reaction/UI/ViewPort/Field/TimeRange.pm
new file mode 100644
index 0000000..3619b5e
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort/Field/TimeRange.pm
@@ -0,0 +1,151 @@
+package Reaction::UI::ViewPort::Field::TimeRange;
+
+use Reaction::Class;
+use Reaction::Types::DateTime;
+use DateTime;
+use DateTime::SpanSet;
+use Time::ParseDate ();
+
+class TimeRange is 'Reaction::UI::ViewPort::Field', which {
+
+ has '+value' => (isa => 'DateTime::SpanSet');
+
+ has '+layout' => (default => 'timerange');
+
+ has value_string =>
+ (isa => 'Str', is => 'rw', lazy_fail => 1, trigger_adopt('value_string'));
+
+ has delete_label => (
+ isa => 'Str', is => 'rw', required => 1, default => sub { 'Delete' },
+ );
+
+ has parent => (
+ isa => 'Reaction::UI::ViewPort::TimeRangeCollection',
+ is => 'ro',
+ required => 1,
+ is_weak_ref => 1
+ );
+
+ implements build_value_string => as {
+ my $self = shift;
+ #return '' unless $self->has_value;
+ #return $self->value_string;
+ };
+
+ implements value_array => as {
+ my $self = shift;
+ return split(',', $self->value_string);
+ };
+
+ implements adopt_value_string => as {
+ my ($self) = @_;
+ my @values = $self->value_array;
+ for my $idx (0 .. 3) { # last value is repeat
+ if (length $values[$idx]) {
+ my ($epoch) = Time::ParseDate::parsedate($values[$idx], UK => 1);
+ $values[$idx] = DateTime->from_epoch( epoch => $epoch );
+ }
+ }
+ $self->value($self->range_to_spanset(@values));
+ };
+
+ implements range_to_spanset => as {
+ my ($self, $time_from, $time_to, $repeat_from, $repeat_to, $pattern) = @_;
+ my $spanset = DateTime::SpanSet->empty_set;
+ if (!$pattern || $pattern eq 'none') {
+ my $span = DateTime::Span->from_datetimes(
+ start => $time_from, end => $time_to
+ );
+ $spanset = $spanset->union( $span );
+ } else {
+ my $duration = $time_to - $time_from;
+ my %args = ( days => $time_from->day + 2,
+ hours => $time_from->hour,
+ minutes => $time_from->minute,
+ seconds => $time_from->second );
+
+ delete $args{'days'} if ($pattern eq 'daily');
+ delete @args{qw/hours days/} if ($pattern eq 'hourly');
+ $args{'days'} = $time_from->day if ($pattern eq 'monthly');
+ my $start_set = DateTime::Event::Recurrence->$pattern( %args );
+ my $iter = $start_set->iterator( start => $repeat_from, end => $repeat_to );
+ while ( my $dt = $iter->next ) {
+ my $endtime = $dt + $duration;
+ my $new_span = DateTime::Span->from_datetimes(
+ start => $dt,
+ end => $endtime
+ );
+ $spanset = $spanset->union( $new_span );
+ }
+ }
+ return $spanset;
+ };
+
+ implements delete => as {
+ my ($self) = @_;
+ $self->parent->remove_range_vp($self);
+ };
+
+ override accept_events => sub { ('value_string', 'delete', super()) };
+
+};
+
+1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::Field::TimeRange
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 value
+
+ Accessor for a L<DateTime::SpanSet> object.
+
+=head2 value_string
+
+ Returns: Encoded range string representing the value.
+
+=head2 value_array
+
+ Returns: Arrayref of the elements of C<value_string>.
+
+=head2 parent
+
+ L<Reaction::UI::ViewPort::TimeRangeCollection> object.
+
+=head2 range_to_spanset
+
+ Arguments: $self, $time_from, $time_to, $repeat_from, $repeat_to, $pattern
+ where $time_from, $time_to, $repeat_from, $repeat_to are L<DateTime>
+ objects, and $pattern is a L<DateTime::Event::Recurrence> method name
+
+ Returns: $spanset
+
+=head2 delete
+
+ Removes TimeRange from C<parent> collection.
+
+=head2 delete_label
+
+ Label for the delete option. Default: 'Delete'.
+
+=head1 SEE ALSO
+
+=head2 L<Reaction::UI::ViewPort::Field>
+
+=head2 L<Reaction::UI::ViewPort::TimeRangeCollection>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut