Run   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 65
dl 0
loc 135
rs 10
c 0
b 0
f 0
wmc 23

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getTasks() 0 19 5
A __construct() 0 6 1
A configure() 0 6 1
A format() 0 11 2
A execute() 0 22 3
A getBranch() 0 9 2
A runTasks() 0 19 3
A checkEnvironment() 0 22 6
1
<?php
2
3
namespace Bdelespierre\GitStats\Commands;
4
5
use Bdelespierre\GitStats\Interfaces\GitServiceInterface;
6
use Bdelespierre\GitStats\Interfaces\ProcessServiceInterface;
7
use Bdelespierre\GitStats\Task;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
14
class Run extends Command
15
{
16
    protected GitServiceInterface $git;
17
    protected ProcessServiceInterface $process;
18
19
    public function __construct(GitServiceInterface $git, ProcessServiceInterface $process)
20
    {
21
        $this->git = $git;
22
        $this->process = $process;
23
24
        parent::__construct();
25
    }
26
27
    protected function configure()
28
    {
29
        $this->setName('run')
30
            ->setDescription('Iterate through git commits to gather statistics')
31
            ->addArgument('branch', InputArgument::OPTIONAL, "The branch to use.", "master")
32
            ->addArgument('tasks', InputArgument::OPTIONAL, "The task file to use.", ".gitstats.php");
33
    }
34
35
    protected function execute(InputInterface $input, OutputInterface $output)
36
    {
37
        try {
38
            $this->checkEnvironment();
39
40
            $branch = $this->getBranch($input);
41
            $data = $this->runTasks(
42
                $this->getTasks($input),
43
                $this->git->getCommits($branch)
44
            );
45
46
            $output->write($this->format($data));
47
        } catch (\Exception $e) {
48
            $output->writeln("<error>{$e->getMessage()}</error>");
49
            return self::FAILURE;
50
        } finally {
51
            if (isset($branch)) {
52
                $this->git->checkout($branch);
53
            }
54
        }
55
56
        return self::SUCCESS;
57
    }
58
59
    private function checkEnvironment(): void
60
    {
61
        if (! $this->git->isGitAvailable()) {
62
            throw new \RuntimeException("Cannot proceed: unable to find git command.");
63
        }
64
65
        if (! $this->git->isGitRepository()) {
66
            throw new \RuntimeException(sprintf("Cannot proceed: %s is not a git repository.", getcwd()));
67
        }
68
69
        if (! $this->git->updateIndex()) {
70
            throw new \RuntimeException("Cannot proceed: unable to update index.");
71
        }
72
73
        if ($this->git->hasUnstagedChanges()) {
74
            throw new \RuntimeException("Cannot proceed: you have unstaged changes. " .
75
                "Please commit or stash them.");
76
        }
77
78
        if ($this->git->hasUncommittedChanges()) {
79
            throw new \RuntimeException("Cannot proceed: your index contains uncommitted changes. " .
80
                "Please commit or stash them.");
81
        }
82
    }
83
84
    private function getBranch(InputInterface $input): string
85
    {
86
        $branch = $input->getArgument('branch');
87
88
        if (! $this->git->isValidBranch($branch)) {
0 ignored issues
show
Bug introduced by
It seems like $branch can also be of type null and string[]; however, parameter $branch of Bdelespierre\GitStats\In...erface::isValidBranch() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

88
        if (! $this->git->isValidBranch(/** @scrutinizer ignore-type */ $branch)) {
Loading history...
89
            throw new \RuntimeException("Branch {$branch} is not valid.");
90
        }
91
92
        return $branch;
93
    }
94
95
    private function getTasks(InputInterface $input): array
96
    {
97
        $file = $input->getArgument('tasks');
98
99
        if (! is_readable($file)) {
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type null and string[]; however, parameter $filename of is_readable() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

99
        if (! is_readable(/** @scrutinizer ignore-type */ $file)) {
Loading history...
100
            throw new \RuntimeException("File {$file} does not exists.");
101
        }
102
103
        $tasks = [];
104
105
        foreach ((require $file)['tasks'] ?? [] as $name => $task) {
106
            if (! is_array($task) || ! isset($task['command'])) {
107
                $task = ['command' => $task];
108
            }
109
110
            $tasks[] = new Task($this->process, $name, $task['command'], $task['patterns'] ?? []);
111
        }
112
113
        return $tasks;
114
    }
115
116
    private function runTasks(iterable $tasks, iterable $commits): \Generator
117
    {
118
        // headers
119
        yield array_merge(['commit', 'date'], ...array_map(fn($task) => $task->getHeaders(), $tasks));
0 ignored issues
show
Bug introduced by
$tasks of type iterable is incompatible with the type array expected by parameter $array of array_map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
        yield array_merge(['commit', 'date'], ...array_map(fn($task) => $task->getHeaders(), /** @scrutinizer ignore-type */ $tasks));
Loading history...
120
121
        foreach ($commits as $commit) {
122
            $this->git->checkout($commit);
123
124
            $timestamp = $this->git->getCommitTimestamp($commit);
125
            $data = [
126
                'commit' => $commit,
127
                'date' => date('Y-m-d H:i:s', $timestamp),
128
            ];
129
130
            foreach ($tasks as $task) {
131
                $data += $task->run($commit)->toArray();
132
            }
133
134
            yield $data;
135
        }
136
    }
137
138
    private function format(iterable $data): \Generator
139
    {
140
        $buffer = fopen('php://temp', 'r+');
141
142
        foreach ($data as $row) {
143
            $length = fputcsv($buffer, $row, ',', '"', '\\');
144
            fseek($buffer, ftell($buffer) - $length);
145
            yield fgets($buffer);
146
        }
147
148
        fclose($buffer);
149
    }
150
}
151