The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package BugCli;

our $VERSION = '0.2';
use base qw(Term::Shell);
use BugCli::Help;
use BugCli::Summaries;
use BugCli::Completions;
use BugCli::Config;
use strict;
use Getopt::Std;
use FileHandle;
use DBIx::SQLEngine;
use Data::Dumper;
use POSIX qw(strftime);
use Text::Table;
use Config::Tiny;
use Term::ANSIColor qw(:constants);

$Term::ANSIColor::AUTORESET = 1;

our ( $config, $dbh, %login_to_uid, %uid_to_login, $llogin, %lastbugs,
    $lastquery, $hostname );
our (%Query) = ( "AutoIncrement" => 'select LAST_INSERT_ID()', );

sub process_bind {
    my ( $self, $cmd ) = @_;
    $self->cmd( $config->{bind}->{$cmd} );
}

sub init_mysql {
    my ($self) = @_;
    $self->{API}{check_idle} =
      exists $config->{server}->{timeout} ? $config->{server}->{timeout} : 60;
    foreach my $cmd ( keys %{ $config->{command} } ) {
        my ($code) = ord eval "\"$config->{command}->{$cmd}\"";
        $self->term()
          ->add_defun( 'process_bind', sub { process_bind( $self, $cmd ) },
            $code );
    }
    $dbh = DBIx::SQLEngine->new(
        "DBI:mysql:$config->{server}->{table}:$config->{server}->{host}",
        $config->{server}->{user},
        $config->{server}->{password},
        {
            RaiseError         => 1,
            ShowErrorStatement => 1,
        }
    );
    my ($uth) = $dbh->fetch_select(
        table   => 'profiles',
        columns => [ 'userid', 'login_name' ]
    );

    foreach my $uid ( @{$uth} ) {
        $login_to_uid{ $$uid{login_name} } = $$uid{userid};
        $uid_to_login{ $$uid{userid} }     = $$uid{login_name};
    }
    $llogin = $config->{server}->{login};
}

sub idle {
    %lastbugs = query_to_bugs( $dbh->fetch_select( sql => $lastquery ) )
      if defined $lastquery;
}

sub run_history {
    my ($self) = @_;
    print join( "\n", $self->term()->GetHistory() ) . "\n";

}

sub add_bug {
    my ( $dbh, $assignee, $subject, $text, $version, $milestone, $severity ) =
      @_;
    my ($time) = strftime "%Y-%m-%d %H:%M:%S", localtime;
    my ($sth) = $dbh->do_insert(
        'table'   => 'bugs',
        'columns' => [
            qw(assigned_to bug_severity bug_status creation_ts short_desc product_id reporter version target_milestone component_id)
        ],
        'values' => [
            $login_to_uid{$assignee},         $severity,
            $time,                            $subject,
            $config->{product}->{product_id}, $login_to_uid{$llogin},
            $version,                         $milestone,
            $config->{product}->{component_id}
        ]
    );
    my ($bugid) = $dbh->fetch_one_value( sql => $Query{AutoIncrement} );
    update_bug( $dbh, $bugid, undef, undef, $text );
    return $bugid;
}

sub update_bug {
    my ( $dbh, $bug_id, $bug_resolution, $bug_status, $text ) = @_;
    if ( defined $bug_resolution ) {
        $dbh->do_update(
            'table'    => 'bugs',
            'values'   => { resolution => "'$bug_resolution'" },
            'criteria' => { bug_id => $bug_id }
        );
    }
    if ( defined $bug_status ) {
        $dbh->do_update(
            'table'    => 'bugs',
            'values'   => { bug_status => "'$bug_status'" },
            'criteria' => { bug_id => $bug_id }
        );
    }

    my ($time) = strftime "%Y-%m-%d %H:%M:%S", localtime;
    $dbh->do_insert(
        'table'   => 'longdescs',
        'columns' => [qw(bug_id who bug_when work_time thetext)],
        'values'  => [ $bug_id, $login_to_uid{$llogin}, "'$time'", 0.00, $text ]
    );
}

#only delete a certain description

sub bugs_to_table {
    my (%bugs) = @_;
    my (@b);
    foreach my $bid ( keys %bugs ) {
        push @b,
          [
            $bid,                      $bugs{$bid}->{bug_severity},
            $bugs{$bid}->{bug_status}, $bugs{$bid}->{short_desc},
            $bugs{$bid}->{version},    $bugs{$bid}->{resolution}
          ];
    }
    return @b;
}

sub query_to_bugs {
    my ($sth) = @_;
    my (%bugs);
    foreach my $a ( @{$sth} ) {
        $bugs{ $$a{bug_id} } = $a;
        $bugs{ $$a{bug_id} }{COMMENTS} = get_comments( $dbh, $$a{bug_id} );
    }

    return %bugs;

}

sub print_bug {
    my ( $id, $bug ) = @_;
    return <<EOT;
=====================
Bug #$id Information
Subject:    $$bug{short_desc}
Severity:   $$bug{bug_severity}
Status:     $$bug{bug_status}
Resolution: $$bug{resolution}
Comments: 
$$bug{COMMENTS}
=====================
EOT
}

sub get_comments {
    my ( $dbh, $bugid ) = @_;
    my ($text);
    my ($bth) = $dbh->fetch_select(
        table    => 'longdescs',
        criteria => { bug_id => $bugid },
        columns  => [qw(who bug_when thetext)]
    );

    foreach my $z ( @{$bth} ) {
        next if not defined $$z{thetext} or
	        $$z{thetext} =~ /^\n$/ or
		$$z{thetext} =~ /^\s*(:?done|fixed|\.)\s*$/i;
        $text .= "\n-------------------\n";
        $text .= "From: $uid_to_login{$$z{who}} When: $$z{bug_when}\n";
        $text .= $$z{thetext};
    }
    return $text;

}

sub prompt_str { "$config->{server}->{login}" . BLUE . "\@" . RESET . UNDERLINE . 
	         "$config->{server}->{host}> " }
	 
sub alias_exit { 'q', 'quit' }

sub prepare_query {
    my ( $fmt, $regexp ) = @_;
    $fmt =~ s/\%uid/$login_to_uid{$llogin}/g;
    $fmt =~ s/\%regexp/$regexp/g;
    return $fmt;
}

sub run_changelog {
    my ($self, $date, @param) = @_;
    my(@ta) = @ARGV;
    @ARGV = @param;
    my(%opts,$fh);
    getopts('fo:',\%opts);
    @ARGV = @ta;
    my($sql) = "SELECT * FROM bugs WHERE (lastdiffed > '" . range_to_date($date) . "' AND assigned_to = $login_to_uid{$llogin} AND  resolution = 'FIXED')";
    my(%bugz) = query_to_bugs( $dbh->fetch_select(sql=>$sql ) );
    $fh = new FileHandle; 	       
    if($opts{o}){
	    $fh->open("> $opts{o}");
    }else{
	    $fh->open(">&STDOUT");
    }
    foreach my $bugid (sort keys %bugz) {
	    print $fh "[BUG #$bugid] $bugz{$bugid}{short_desc}\n";
	    if($opts{f}){
		    print $fh "$bugz{$bugid}{COMMENTS}\n";
	    }
	    
    }
    print "Output written to $opts{o} \n" if $opts{o};
    $fh->close;
}

sub range_to_date {
	my($scale) = @_;
	# general format: "[d<day-of-mont/day-of-week>][w<week-of-year>][m<month>][y<year>]
	# default month,year - current
	# month-week mutualy excluded.
	# monthes - 0-11; weekdays - 0-6; monthdays 1-31
	# week #0 is week containgin 1 Jan. 
	# negative value - backward from this moment i.e. kw-1 - previous week
	# TODO ranges - like km2-4 for spring.
	if (my ($day1,$week1,$mon1,$year1) = $scale =~ /^(?:d(-?\d+))?(?:w(-?\d+))?(?:m(-?\d+))?(?:y(-?\d+))?$/) {
	    #	    Error "wrong kalendar range given: $scale", return undef unless
	    #		defined $day1 || ( defined $week1 && !defined $mon1);

  	    my(undef,undef,undef,$cur_day,$cur_mon,$cur_year,$cur_wday,$cur_yday) = localtime;
	    my($day,$mon,$dd,$dm,$week2) = (1,0,0,0);
	    my($year) = !defined $year1 ? $cur_year :
	            $year1 > 1000 ? $year1-1900 : 
	            $year1 > 0 ? $year1 :
		    $cur_year + $year1;

	    $mon1 = $cur_mon+$mon1 if defined $mon1 && $mon1=~/^-/;
	    
	    if (defined $week1) {
		my $f_wday = (localtime POSIX::mktime(0,0,0,0,0,$year,0,0,-1))[6];
		if ($week1 !~ /^-/) {
		    $week2 = $week1;
		    $week1 = $week1 * 7 - $f_wday;
		} else { 
                    $week1 = $cur_yday + 1 + $week1 * 7 - $cur_wday;
		    $week2 = int(($week1 + $f_wday) / 7);
		}
	    }

	    my $k;

	    if (defined $day1) {
		$dd = 1;
	        $day = $day1 > 0 ? $day1 : $cur_day + $day1;
		$k .= "d$day";
		if (defined $week1) {
                    $day += $week1;
		    $k .= "w$week2";
		} elsif (defined $mon1) {
                    $mon = $mon1;
		    $k .= "m$mon";
		} else {
                    $mon = $cur_mon;
		    $k .= "m$mon";
		}
	    } elsif (defined $week1) {
	        $k .= "w$week2";
		$dd = 7;
		$day = $week1;
	    } elsif (defined $mon1) {
		$dm = 1;
                $mon = $mon1;
	        $k .= "m$mon";
	    } else {
		$dm = 12;
	    }

	    $k .= "y$year";

	    return strftime("%Y-%m-%d %H:%M:%S", localtime (POSIX::mktime(0,0,0,$day,$mon,$year,0,0,-1)));
    }
}

# delete command declaration {{{
sub run_delete {
    my ( $self, $comm, @params ) = @_;
    if ( $comm =~ /^\d+$/ ) {
        print "BUG: ID#$comm TITLE:$lastbugs{$comm}->{short_desc}\n";
        my ($ans);
        if ( $params[0] ne '-f' ) {
            $ans =
              $self->prompt( "Are you sure that you want to delete this bug?",
                "Y" );
        }
        else { $ans = 'y' }
        if ( $ans =~ /^y/i ) {
            $dbh->query("DELETE FROM bugs WHERE bug_id=$comm");
            $dbh->query("DELETE FROM longdescs WHERE bug_id=$comm");
        }
        print "Bug deleted.\n";
    }

}

# }}}

# show command declarations {{{

sub run_show {
    my ( $self, $comm ) = @_;
    my (@bugids);
    if ( $comm =~ /^\d+$/ ) {    #ok. just show by bugid
        push @bugids, $comm;
    }
    elsif ( $comm =~ m#^/(.*)/$# ) {    #ok, got regexp. let's find em!
        my ($r) = $1;
        my (%a) = query_to_bugs(
            $dbh->fetch_select(
                sql => "SELECT bug_id FROM bugs where short_desc REGEXP '$1'"
            )
        );

        push @bugids, keys %a;
    }
    else {
        print "Whoopsie! Bad syntax. See 'help show' for explainations.\n";
        return;
    }
    foreach my $bugid (@bugids) {
        my (%b) = query_to_bugs(
            $dbh->fetch_select(
                table    => 'bugs',
                criteria => { 'bug_id' => $bugid }
            )
        );
        $self->page( print_bug( $bugid, $b{$bugid} ) );
        print "Press any key to continue...";
        getc;
    }

}

sub run_take {
    push @_, undef if scalar(@_) < 3;
    do_fix( @_, undef, undef, undef, 'take' );
}

sub run_comment {
    push @_, undef if scalar(@_) < 3;
    do_fix( @_, undef, undef, undef, 'comment' );
}

sub run_fix {
    push @_, undef if scalar(@_) < 3;
    do_fix( @_, undef, undef, undef, 'fix' );
}


sub do_fix {
    my ( $self, $bugid, $text, undef, undef, undef, $cmd ) = @_;
    if ( defined $bugid && $bugid =~ /^\d+/ ) {
        my (%b) = query_to_bugs(
            $dbh->fetch_select(
                table    => 'bugs',
                criteria => { 'bug_id' => $bugid }
            )
        );
        print "No such bug!\n", return if not keys %b;
        if ( not defined $text ) {
            print "Please enter a comment: (Ctrl-D to finish) \n";
            $text = join '', <STDIN>;
        }
        print "You should enter some comments!", return if $text eq '';
        if ( $cmd eq 'fix') {
            update_bug( $dbh, $bugid, 'FIXED', 'RESOLVED', $text );
        }
        elsif ( $cmd eq 'comment' ) {
            update_bug( $dbh, $bugid, undef, undef, $text );
        }
        elsif ( $cmd eq 'take' ) {
            update_bug( $dbh, $bugid, undef, 'ASSIGNED', $text );
        }
    }
    else {
        print "Incorrect syntax! Read 'help fix'. \n";
    }

}

# }}}

# bugs command declarations {{{
sub run_bugs {
    my ( $self, $query, $param ) = @_;
    my $tb =
      Text::Table->new(qw|ID SEVERITY STATUS SUBJECT VERSION RESOLUTION|);
    if ($query) {
        my ($q);
        if ( $query =~ m#^/(.*)/$# ) {    #ok user wants a regexp
            $q =
"SELECT * from $config->{server}->{table} where short_desc regexp '$1'";
        }
        if ( exists $config->{query}->{$query} ) {
            $q = prepare_query( $config->{query}->{$query}, $param );
        }
        if ($q) {
            %lastbugs = query_to_bugs( $dbh->fetch_select( sql => $q ) );
            $lastquery = $q;
            my (@b) = bugs_to_table(%lastbugs);
            $tb->load(@b);
        }
        else {
            print "Such query is not defined!\n";
        }
      
    print "\n$tb";
    }
    else {
	$self->config_show_query(1);
    }

}

1;