1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
|
package UR::Object::Command::FetchAndDo;
use strict;
use warnings;
use UR;
our $VERSION = "0.47"; # UR $VERSION;
use Data::Dumper;
class UR::Object::Command::FetchAndDo {
is => 'Command',
is_abstract => 1,
has => [
subject_class => {
is => 'UR::Object::Type',
id_by => 'subject_class_name',
},
filter => {
is => 'Text',
is_optional => 1,
doc => 'Filter results based on the parameters. See below for how to.'
},
_fields => {
is_many => 1,
is_optional => 1,
doc => 'Methods which the caller intends to use on the fetched objects. May lead to pre-fetching the data.'
},
],
};
########################################################################
sub help_detail {
my $class = shift;
return $class->_filter_doc;
}
sub _filter_doc {
my $class = shift;
my $doc = <<EOS;
Filtering:
----------
Create filter equations by combining filterable properties with operators and
values.
Combine and separate these 'equations' by commas.
Use single quotes (') to contain values with spaces: name='genome institute'
Use percent signs (%) as wild cards in like (~).
Use backslash or single quotes to escape characters which have special meaning
to the shell such as < > and &
Operators:
----------
= (exactly equal to)
~ (like the value)
: (in the list of several values, slash "/" separated)
(or between two values, dash "-" separated)
> (greater than)
>= (greater than or equal to)
< (less than)
<= (less than or equal to)
Examples:
---------
EOS
if (my $help_synopsis = $class->help_synopsis) {
$doc .= " $help_synopsis\n";
} else {
$doc .= <<EOS
lister-command --filter name=Bob --show id,name,address
lister-command --filter name='something with space',employees\>200,job~%manager
lister-command --filter cost:20000-90000
lister-command --filter answer:yes/maybe
EOS
}
$doc .= <<EOS;
Filterable Properties:
----------------------
EOS
# Try to get the subject class name
my $self = $class->create;
if ( not $self->subject_class_name
and my $subject_class_name = $self->_resolved_params_from_get_options->{subject_class_name} ) {
$self = $class->create(subject_class_name => $subject_class_name);
}
if ( $self->subject_class_name ) {
if ( my @properties = $self->_subject_class_filterable_properties ) {
my $longest_name = 0;
foreach my $property ( @properties ) {
my $name_len = length($property->property_name);
$longest_name = $name_len if ($name_len > $longest_name);
}
for my $property ( @properties ) {
my $property_doc = $property->doc;
unless ($property_doc) {
eval {
foreach my $ancestor_class_meta ( $property->class_meta->ancestry_class_metas ) {
my $ancestor_property_meta = $ancestor_class_meta->property_meta_for_name($property->property_name);
if ($ancestor_property_meta and $ancestor_property_meta->doc) {
$property_doc = $ancestor_property_meta->doc;
last;
}
}
};
}
$property_doc ||= ' (undocumented)';
$property_doc =~ s/\n//gs; # Get rid of embeded newlines
my $data_type = $property->data_type || '';
$data_type = ucfirst(lc $data_type);
$doc .= sprintf(" %${longest_name}s ($data_type): $property_doc\n",
$property->property_name);
}
}
else {
$doc .= sprintf(" %s\n", $self->error_message);
}
}
else {
$doc .= " Can't determine the list of filterable properties without a subject_class_name";
}
return $doc;
}
########################################################################
sub execute {
my $self = shift;
$self->_validate_subject_class
or return;
my $iterator = $self->_fetch
or return;
return $self->_do($iterator);
}
sub _validate_subject_class {
my $self = shift;
my $subject_class_name = $self->subject_class_name;
$self->error_message("No subject_class_name indicated.")
and return unless $subject_class_name;
$self->error_message(
sprintf(
'This command is not designed to work on a base UR class (%s).',
$subject_class_name,
)
)
and return if $subject_class_name =~ /^UR::/;
UR::Object::Type->use_module_with_namespace_constraints($subject_class_name);
my $subject_class = $self->subject_class;
$self->error_message(
sprintf(
'Can\'t get class meta object for class (%s). Is this class a properly declared UR::Object?',
$subject_class_name,
)
)
and return unless $subject_class;
$self->error_message(
sprintf(
'Can\'t find method (all_property_metas) in %s. Is this a properly declared UR::Object class?',
$subject_class_name,
)
)
and return unless $subject_class->can('all_property_metas');
return 1;
}
sub _subject_class_filterable_properties {
my $self = shift;
$self->_validate_subject_class
or return;
my %props = map { $_->property_name => $_ }
$self->subject_class->property_metas;
return map { $_->[1] } # These maps are to get around a bug in perl 5.8
sort { $a->[0] cmp $b->[0] } # sort involving methdo calls inside the sort sub that
map { [ $_->property_name, $_ ] } # might do sorts of their own
grep { substr($_->property_name, 0, 1) ne '_' } # Skip 'private' properties starting with '_'
grep { ! $_->data_type or index($_->data_type, '::') == -1 } # Can't filter object-type properties from a lister, right?
values %props;
}
sub _hint_string {
return;
}
sub _base_filter {
return;
}
sub _complete_filter {
my $self = shift;
return join(',', grep { defined $_ } $self->_base_filter,$self->filter);
}
sub _fetch
{
my $self = shift;
my ($bool_expr, %extra) = UR::BoolExpr->resolve_for_string(
$self->subject_class_name,
$self->_complete_filter,
$self->_hint_string
);
$self->error_message( sprintf('Unrecognized field(s): %s', join(', ', keys %extra)) )
and return if %extra;
if (my $i = $self->subject_class_name->create_iterator($bool_expr)) {
return $i;
}
else {
$self->error_message($self->subject_class_name->error_message);
return;
}
}
sub _do
{
shift->error_message("Abstract class. Please implement a '_do' method in your subclass.");
return;
}
1;
=pod
=head1 NAME
UR::Object::Command::FetchAndDo - Base class for fetching objects and then performing a function on/with them.
=head1 SYNOPSIS
package MyFecthAndDo;
use strict;
use warnings;
use above "UR";
class MyFecthAndDo {
is => 'UR::Object::Command::FetchAndDo',
has => [
# other properties...
],
};
sub _do { # required
my ($self, $iterator) = @_;
while (my $obj = $iterator->next) {
...
}
return 1;
}
1;
=head1 Provided by the Developer
=head2 _do (required)
Implement this method to 'do' unto the iterator. Return true for success, false for failure.
sub _do {
my ($self, $iterator) = @_;
while (my $obj = $iterator->next) {
...
}
return 1;
}
=head2 subject_class_name (optional)
The subject_class_name is the class for which the objects will be fetched. It can be specified one of two main ways:
=over
=item I<by_the_end_user_on_the_command_line>
For this do nothing, the end user will have to provide it when the command is run.
=item I<by_the_developer_in the_class_declartion>
For this, in the class declaration, add a has key w/ arrayref of hashrefs. One of the hashrefs needs to be subject_class_name. Give it this declaration:
class MyFetchAndDo {
is => 'UR::Object::Command::FetchAndDo',
has => [
subject_class_name => {
value => <CLASS NAME>,
is_constant => 1,
},
],
};
=back
=head2 helps (optional)
Overwrite the help_brief, help_synopsis and help_detail methods to provide specific help. If overwiting the help_detail method, use call '_filter_doc' to get the filter documentation and usage to combine with your specific help.
=cut
#$HeadURL: svn+ssh://svn/srv/svn/gscpan/distro/ur-bundle/trunk/lib/UR/Object/Command/FetchAndDo.pm $
#$Id: FetchAndDo.pm 47408 2009-06-01 03:53:45Z ssmith $#
|