New URL for NEMO forge!   http://forge.nemo-ocean.eu

Since March 2022 along with NEMO 4.2 release, the code development moved to a self-hosted GitLab.
This present forge is now archived and remained online for history.
CODE.pm in vendors/FCM-2017.10.0/lib/FCM/Class – NEMO

source: vendors/FCM-2017.10.0/lib/FCM/Class/CODE.pm @ 12899

Last change on this file since 12899 was 10672, checked in by nicolasmartin, 5 years ago

Reimport latest FCM release

File size: 9.2 KB
Line 
1#-------------------------------------------------------------------------------
2# (C) British Crown Copyright 2006-17 Met Office.
3#
4# This file is part of FCM, tools for managing and building source code.
5#
6# FCM is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# FCM is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with FCM. If not, see <http://www.gnu.org/licenses/>.
18#-------------------------------------------------------------------------------
19use strict;
20use warnings;
21
22#-------------------------------------------------------------------------------
23package FCM::Class::CODE;
24use FCM::Class::Exception;
25use Scalar::Util qw{reftype};
26
27# Methods for working out the default value of an attribute.
28my %ATTRIB_DEFAULT_BY = (
29    default => sub {
30        my $opt_ref = shift();
31        my $ret = $opt_ref->{default};
32        return (ref($ret) && reftype($ret) eq 'CODE' ? $ret->() : $ret);
33    },
34    isa     => sub {
35        my $opt_ref = shift();
36        return
37              $opt_ref->{isa} eq 'ARRAY' ? []
38            : $opt_ref->{isa} eq 'HASH'  ? {}
39            : $opt_ref->{isa} eq 'CODE'  ? sub {}
40            :                              undef
41            ;
42    },
43);
44
45# Checks the value of an attribute.
46my $ATTRIB_CHECK = sub {
47    my ($class, $opt_ref, $key, $value, $caller_ref) = @_;
48    # Note: undef is always OK?
49    if (!defined($value)) {
50        return;
51    }
52    my $expected_isa = $opt_ref->{isa};
53    if (!$expected_isa || $expected_isa eq 'SCALAR' && !ref($value)) {
54        return;
55    }
56    if (!UNIVERSAL::isa($value, $expected_isa)) {
57        return FCM::Class::Exception->throw({
58            'code'    => FCM::Class::Exception->CODE_TYPE,
59            'caller'  => $caller_ref,
60            'package' => $class,
61            'key'     => $key,
62            'type'    => $expected_isa,
63            'value'   => $value,
64        });
65    }
66};
67
68# Creates the methods of the class.
69sub class {
70    my ($class, $attrib_opt_ref, $class_opt_ref) = @_;
71    my %class_opt = (
72        init        => sub {},
73        init_attrib => sub {@_},
74        action_of   => {},
75        (defined($class_opt_ref) ? %{$class_opt_ref} : ()),
76    );
77    if (!defined($attrib_opt_ref)) {
78        $attrib_opt_ref = {};
79    }
80    my %attrib_opt;
81    while (my ($key, $item) = each(%{$attrib_opt_ref})) {
82        my %option = (
83            r       => 1,     # readable?
84            w       => 1,     # writable?
85            default => undef, # default value or CODE to return it
86            isa     => undef, # attribute isa
87            (     defined($item) && ref($item) ? %{$item}
88                : defined($item)               ? (isa => $item)
89                :                                ()
90            ),
91        );
92        if (defined($option{isa})) {
93            $option{isa}
94                = $option{isa} eq '$' ? 'SCALAR'
95                : $option{isa} eq '@' ? 'ARRAY'
96                : $option{isa} eq '%' ? 'HASH'
97                : $option{isa} eq '&' ? 'CODE'
98                : $option{isa} eq '*' ? 'GLOB'
99                :                       $option{isa}
100                ;
101        }
102        $attrib_opt{$key} = \%option;
103    }
104    my $main_ref = sub {
105        my ($attrib_ref, $key, @args) = @_;
106        if (!exists($class_opt{action_of}{$key})) {
107            return;
108        }
109        $class_opt{action_of}{$key}->($attrib_ref, @args);
110    };
111    no strict qw{refs};
112    # $class->new(\%attrib)
113    *{$class . '::new'} = sub {
114        my $class = shift();
115        my ($attrib_ref) = $class_opt{init_attrib}->(@_);
116        my $caller_ref = [caller()];
117        my %attrib = (defined($attrib_ref) ? %{$attrib_ref} : ());
118        while (my ($key, $value) = each(%attrib)) {
119            if (exists($attrib_opt{$key})) {
120                $ATTRIB_CHECK->(
121                    $class, $attrib_opt{$key}, $key, $value, $caller_ref,
122                );
123            }
124            #else {
125            #    delete($attrib{$key});
126            #}
127        }
128        my $self = bless(sub {$main_ref->(\%attrib, @_)}, $class);
129        KEY:
130        while (my ($key, $opt_ref) = each(%attrib_opt)) {
131            if (exists($attrib{$key})) {
132                next KEY;
133            }
134            for my $opt_name (qw{default isa}) {
135                if (defined($opt_ref->{$opt_name})) {
136                    $attrib{$key} = $ATTRIB_DEFAULT_BY{$opt_name}->($opt_ref);
137                    next KEY;
138                }
139            }
140        }
141        $class_opt{init}->(\%attrib, $self);
142        return $self;
143    };
144    # $instance->$key()
145    for my $key (keys(%{$class_opt{action_of}})) {
146        *{$class . '::' . $key}
147            = sub {my $self = shift(); $self->($key, @_)};
148    }
149    return 1;
150}
151
152#-------------------------------------------------------------------------------
1531;
154__END__
155
156=head1 NAME
157
158FCM::Class::CODE
159
160=head1 SYNOPSIS
161
162    # Example
163    package Bar;
164    use base qw{FCM::Class::CODE};
165    __PACKAGE__->class(
166        {
167            # ...
168        },
169        {
170            action_of => {
171                bend => sub {
172                    my ($attrib_ref, @args) = @_;
173                    # ...
174                },
175                stretch => sub {
176                    my ($attrib_ref, @args) = @_;
177                    # ...
178                },
179            },
180        },
181    );
182    # Some time later...
183    $bar = Bar->new(\%attrib);
184    $bar->bend(@args);
185    $bar->stretch(@args);
186
187=head1 DESCRIPTION
188
189Provides a simple method to create CODE-based classes.
190
191=head1 METHODS
192
193=over 4
194
195=item $class->class(\%attrib_opt,\%class_opt)
196
197Creates common methods for a CODE-based class.
198
199The %attrib_opt is used to configure the attributes of an instance of the class.
200The key of each element is the name of the attribute, and the value is a HASH
201containing the options of the attribute, or a SCALAR. (If a SCALAR is specified,
202it is equivalent to {isa => value}.) The options may contain:
203
204=over 4
205
206=item r
207
208(Default=true) If true, the attribute is readable.
209
210=item w
211
212(Default=true) If true, the attribute is writable.
213
214=item default
215
216(Default=undef) The default value of the attribute.
217
218If this option is defined, the attribute will be initialised to the specified
219value when the new() method is called. In the special case where the value of
220this option is a CODE reference, it will be invoked as $code->(\%attrib), and
221the default value will be the returned value of the CODE reference. This is
222useful, for example, if the default value needs to be a new instance of a class.
223If a genuine CODE reference is required as the default, this option should be
224set to a CODE reference that returns the required CODE reference itself.
225
226For example:
227
228    Foo->class({
229        foo => {default => 'foo'},          # 'foo'
230        bar => {default => sub {get_id()}}, # the next id
231        baz => {default => sub {\&code}},   # &code
232    });
233    {
234        my $id = 0;
235        sub get_id {$id++}
236    }
237
238If a default option is not defined, and if the attribute "isa" is ARRAY, HASH or
239CODE, then the default value is [], {} and sub {} respectively.
240
241=item isa
242
243(Default=undef) The expected type of the attribute. If this optioin is defined
244as $type, a new $value of the attribute is only accepted if $value is undef,
245UNIVERSAL::isa($value,$type) returns true or if $type is C<SCALAR> and the new
246value is not a reference.
247
248The attribute accepts $, @, %, & and * as aliases to SCALAR, ARRAY, HASH, CODE
249and GLOB.
250
251=back
252
253The %class_opt is used to configure what methods are created for the class, as
254well as other options for the $class->new() method. It may contain the
255following:
256
257=over 4
258
259=item init
260
261If $class_opt{init} is defined, it should be a CODE reference. If specified, it
262will be called once when $instance->new() is called, with the interface
263$init->(\%attrib,$self).
264
265=item init_attrib
266
267The value of this option must be a CODE. The $class->new() normally expects a
268single HASH reference argument. If an alternate interface to the $class->new()
269is required, this CODE can be used to turn the input argument list to the
270expected HASH reference.
271
272=item action_of
273
274This provides the actions of the class. It should be a HASH. Each $key in the
275HASH will be turned into a method implemented by the CODE reference in the
276corresponding $value: $instance->$key(@args) will call $instance->($key,@args),
277which will call $value->(\%attrib,@args).
278
279=back
280
281=item $class->new(\%attrib)
282
283Creates a new instance with %attrib. Initial values of the attributes can be
284specified using %attrib. Otherwise, the method will attempt to assign the
285default values, as specified in the class() method, to the newly created
286instance.
287
288=item $instance->$key(@args)
289
290A method is created for each $key of the %{$attrib{action_of}}.
291
292=back
293
294=head1 DIAGNOSTICS
295
296L<FCM::Class::Exception|FCM::Class::Exception> is thrown on error.
297
298=head1 SEE ALSO
299
300Inspired by the standard module L<Class::Struct|Class::Struct> and CPAN modules
301such as L<Class::Accessor|Class::Accessor>.
302
303=head1 COPYRIGHT
304
305(C) Crown copyright Met Office. All rights reserved.
306
307=cut
Note: See TracBrowser for help on using the repository browser.