1 # This module is part of debbugs, and
2 # is released under the terms of the GPL version 2, or any later
3 # version (at your option). See the file README and COPYING for more
5 # Copyright 2018 by Don Armstrong <don@donarmstrong.com>.
7 package Debbugs::Bug::Status;
11 Debbugs::Bug::Status -- OO interface to status files
16 Debbugs::Bug->new(schema => $s,binaries => [qw(foo)],sources => [qw(bar)]);
27 use v5.10; # for state
28 use Mouse::Util::TypeConstraints qw(enum);
31 use List::AllUtils qw(max first min);
33 use Params::Validate qw(validate_with :types);
34 use Debbugs::Common qw(make_list);
35 use Debbugs::Config qw(:config);
36 use Debbugs::Status qw(get_bug_status);
42 extends 'Debbugs::OOBase';
44 my $meta = __PACKAGE__->meta;
46 has bug => (is => 'ro', isa => 'Int',
49 # status obtained from DB, filesystem, or hashref
50 has status_source => (is => 'ro',
51 isa => enum([qw(db filesystem hashref)]),
52 default => 'filesystem',
53 writer => '_set_status_source',
56 has _status => (is => 'bare',
57 writer => '_set_status',
59 predicate => '_has__status',
67 state $field_mapping =
68 {originator => 'submitter',
70 msgid => 'message_id',
71 blockedby => 'blocked_by',
72 found_versions => 'found',
73 fixed_versions => 'fixed',
75 if (not exists $args->{status} and exists $args->{bug}) {
76 if ($self->has_schema) {
78 $self->schema->resultset('BugStatus')->
79 search_rs({id => [make_list($args->{bug})]},
80 {result_class => 'DBIx::Class::ResultClass::HashRefInflator'})->
82 for my $field (keys %{$field_mapping}) {
83 $args->{status}{$field_mapping->{$field}} =
84 $args->{status}{$field} if defined $args->{status}{$field};
85 delete $args->{status}{$field};
87 $self->_set_status_source('db');
89 $args->{status} = get_bug_status(bug=>$args->{bug});
90 for my $field (keys %{$field_mapping}) {
91 $args->{status}{$field_mapping->{$field}} =
92 $args->{status}{$field} if defined $args->{status}{$field};
94 $self->_set_status_source('filesystem');
96 } elsif (exists $args->{status}) {
97 for my $field (keys %{$field_mapping}) {
98 $args->{status}{$field_mapping->{$field}} =
99 $args->{status}{$field} if defined $args->{status}{$field};
101 $self->_set_status_source('hashref');
103 if (exists $args->{status}) {
104 if (ref($args->{status}) ne 'HASH') {
105 croak "status must be a HASHREF (argument to __PACKAGE__)";
107 $self->_set_status($args->{status});
108 delete $args->{status};
112 has saved => (is => 'ro', isa => 'Bool',
114 writer => '_set_set_saved',
118 my ($self,$field,$default) = @_;
119 if ($self->_has__status) {
120 my $s = $self->_status()->{$field};
121 return $s if defined $s;
130 =head3 Single-value Fields
134 =item submitter (single)
144 $self->__field_or_def('submitter',
145 $config{maintainer_email});
148 writer => '_set_submitter',
161 $self->__field_or_def('date',
165 writer => '_set_date',
168 =item last_modified (single)
178 $self->__field_or_def('last_modified',
182 writer => '_set_last_modified',
185 =item log_modified (single)
195 $self->__field_or_def('log_modified',
199 writer => '_set_log_modified',
213 $self->__field_or_def('subject',
217 writer => '_set_subject',
231 $self->__field_or_def('message_id',
232 'nomessageid.'.$self->date.'_'.
233 md5_hex($self->subject.$self->submitter).
234 '@'.$config{email_domain},
237 writer => '_set_message_id',
254 $self->__field_or_def('severity',
255 $config{default_severity});
257 writer => '_set_severity',
262 Unix epoch the bug was last unarchived. Zero if the bug has never been
274 $self->__field_or_def('unarchived',
277 writer => '_set_unarchived',
282 True if the bug is archived, false otherwise.
293 $self->__field_or_def('archived',
296 writer => '_set_archived',
311 for my $field (qw(owner unarchived summary outlook done forwarded)) {
318 $self->__field_or_def($field,
321 writer => '_set_'.$field,
324 my $field_method = $meta->find_method_by_name($field);
325 die "No field method for $field" unless defined $field_method;
326 $meta->add_method('has_'.$field =>
327 sub {my $self = shift;
328 return length($field_method->($self));
334 =head3 Multi-value Fields
346 for my $field (qw(affects package tags)) {
349 traits => [qw(Array)],
350 isa => 'ArrayRef[Str]',
354 if ($self->_has__status) {
355 my $s = $self->_status()->{$field};
357 $s = _build_split_field($s,
364 writer => '_set_'.$field,
365 handles => {$field => 'elements',
366 $field.'_count' => 'count',
367 $field.'_join' => 'join',
371 my $field_method = $meta->find_method_by_name($field);
372 if (defined $field_method) {
373 $meta->add_method($field.'_ref'=>
374 sub {my $self = shift;
375 return [$field_method->($self)]
386 sub __hashref_field {
387 my ($self,$field) = @_;
389 if ($self->_has__status) {
390 my $s = $self->_status()->{$field};
392 $s = _build_split_field($s,
400 for my $field (qw(found fixed)) {
404 isa => 'HashRef[Str]',
408 if ($self->_has__status) {
409 my $s = $self->_status()->{$field};
411 $s = _build_split_field($s,
414 if (ref($s) ne 'HASH') {
415 $s = {map {$_,'1'} @{$s}};
421 default => sub {return {}},
422 writer => '_set_'.$field,
423 handles => {$field => 'keys',
424 $field.'_count' => 'count',
428 my $field_method = $meta->find_method_by_name($field);
429 if (defined $field_method) {
430 $meta->add_method('_'.$field.'_ref'=>
431 sub {my $self = shift;
432 return [$field_method->($self)]
434 $meta->add_method($field.'_join'=>
435 sub {my ($self,$joiner) = @_;
436 return join($joiner,$field_method->($self));
442 for (qw(found fixed)) {
443 around '_set_'.$_ => sub {
446 if (defined ref($_[0]) and
447 ref($_[0]) eq 'ARRAY'
449 @_ = {map {$_,'1'} @{$_[0]}};
451 @_ = {map {$_,'1'} @_};
467 for my $field (qw(blocks blocked_by mergedwith)) {
471 isa => 'HashRef[Int]',
475 if ($self->_has__status) {
476 my $s = $self->_status()->{$field};
478 $s = _build_split_field($s,
481 if (ref($s) ne 'HASH') {
482 $s = {map {$_,'1'} @{$s}};
488 handles => {$field.'_count' => 'count',
490 writer => '_set_'.$field,
493 my $internal_field_method = $meta->find_method_by_name('_'.$field);
494 die "No field method for _$field" unless defined $internal_field_method;
495 $meta->add_method($field =>
496 sub {my $self = shift;
497 return sort {$a <=> $b}
498 keys %{$internal_field_method->($self)};
500 my $field_method = $meta->find_method_by_name($field);
501 die "No field method for _$field" unless defined $field_method;
502 $meta->add_method('_'.$field.'_ref'=>
503 sub {my $self = shift;
504 return [$field_method->($self)]
506 $meta->add_method($field.'_join'=>
507 sub {my ($self,$joiner) = @_;
508 return join($joiner,$field_method->($self));
512 for (qw(blocks blocked_by mergedwith)) {
513 around '_set_'.$_ => sub {
516 if (defined ref($_[0]) and
517 ref($_[0]) eq 'ARRAY'
519 $_[0] = {map {$_,'1'} @{$_[0]}};
521 @_ = {map {$_,'1'} @{$_[0]}};
531 sub _build_split_field {
532 sub sort_and_unique {
537 if ($all_numeric and $v =~ /\D/) {
540 next if exists $u{$v};
545 return sort {$a <=> $b} @v;
550 sub split_ditch_empty {
551 return grep {length $_} map {split ' '} @_;
554 my ($val,$field) = @_;
557 if ($field =~ /^(package|affects|source)$/) {
558 return [grep {length $_} map lc, split /[\s,()?]+/, $val];
560 return [sort_and_unique(split_ditch_empty($val))];
565 __PACKAGE__->meta->make_immutable;
568 no Mouse::Util::TypeConstraints;
574 # indent-tabs-mode: nil
575 # cperl-indent-level: 4