Completed
Push — master ( 6ab195...1e296c )
by Faiz
02:51
created

SurahCommand   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 28
lcom 2
cbo 12
dl 0
loc 229
ccs 92
cts 92
cp 1
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A configure() 0 27 1
A execute() 0 23 4
A chapter() 0 24 1
A chapters() 0 15 1
A array_chunk_vertical() 0 21 5
A confirmSurahNo() 0 19 6
A closestSurah() 0 6 1
A askSurah() 0 9 1
A parseResult() 0 28 5
A buildAyah() 0 9 2
1
<?php
2
3
namespace FaizShukri\Quran\Commands;
4
5
use FaizShukri\Quran\Exceptions\SurahInvalid;
6
use FaizShukri\Quran\Quran;
7
use FaizShukri\Quran\Supports\Levenshtein;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Helper\HelperSet;
10
use Symfony\Component\Console\Helper\QuestionHelper;
11
use Symfony\Component\Console\Helper\Table;
12
use Symfony\Component\Console\Helper\TableCell;
13
use Symfony\Component\Console\Input\InputArgument;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Symfony\Component\Console\Question\ChoiceQuestion;
17
18
class SurahCommand extends Command
19
{
20
    private $quran;
21
22
    /**
23
     * @codeCoverageIgnore
24
     */
25
    public function __construct()
26
    {
27
        parent::__construct();
28
        $this->quran = new Quran();
29
    }
30
31
    /**
32
     * @codeCoverageIgnore
33
     */
34
    protected function configure()
35
    {
36
        $this
37
            ->setName('surah')
38
            ->setDescription('Retrieve surah')
39
            ->addArgument(
40
                'surah',
41
                InputArgument::OPTIONAL,
42
                'Specify surah'
43
            )
44
            ->addArgument(
45
                'ayah',
46
                InputArgument::OPTIONAL,
47
                'Specify ayah'
48
            )
49
            ->addArgument(
50
                'translation',
51
                InputArgument::OPTIONAL,
52
                'Specify ayah'
53
            )
54
            ->addUsage('2')
55
            ->addUsage('2 3')
56
            ->addUsage('2 3,5 en')
57
            ->addUsage('baqara 3-5')
58
            ->addUsage('baqara 3,5-6 ar,en')
59
        ;
60
    }
61
62 33
    protected function execute(InputInterface $input, OutputInterface $output)
63
    {
64 33
        $surah = $input->getArgument('surah');
65 33
        $ayah = $input->getArgument('ayah');
66 33
        $translation = $input->getArgument('translation');
67
68 33
        $surah = $this->confirmSurahNo($surah, $input, $output);
0 ignored issues
show
Bug introduced by
It seems like $surah can also be of type array<integer,string>; however, FaizShukri\Quran\Command...mmand::confirmSurahNo() does only seem to accept integer|string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
69
70 30
        if ($ayah) {
71 18
            if ($translation) {
72 15
                $this->quran->translation($translation);
73
            }
74
75 18
            $ayah = $this->quran->get($surah.':'.$ayah);
76 18
            $output->writeln($this->parseResult($ayah));
77 12
        } elseif ($surah) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $surah of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
78 9
            $this->chapter($output, $surah);
79
        } else {
80 3
            $this->chapters($output);
81
        }
82
83 30
        return 0;
84
    }
85
86 9
    private function chapter($output, $verse)
87
    {
88 9
        $surah = $this->quran->getSource()->surah($verse);
89
90 9
        $table = new Table($output);
91
        $table
92 9
            ->setHeaders([
93 9
                [new TableCell('Surah '.$surah['tname'], array('colspan' => 2))],
94
            ])
95 9
            ->setRows([
96 9
                ['Index',  $surah['index']],
97 9
                ['Name',  $surah['tname']],
98 9
                ['Name (ar)',  $surah['name']],
99 9
                ['Meaning',  $surah['ename']],
100 9
                ['No. Ayah',  $surah['ayas']],
101 9
                ['Start',  $surah['start']],
102 9
                ['Type',  $surah['type']],
103 9
                ['Order',  $surah['order']],
104 9
                ['Rukus',  $surah['rukus']],
105
            ])
106 9
            ->setStyle('borderless')
107
        ;
108 9
        $table->render();
109 9
    }
110
111 3
    private function chapters($output)
112
    {
113 3
        $surah = $this->quran->getSource()->surah();
114
        $surah = array_map(function ($sura) { return "$sura->index. $sura->tname"; }, $surah);
115 3
        $surah = $this->array_chunk_vertical($surah, 4);
116
117 3
        $table = new Table($output);
118
        $table
119 3
            ->setHeaders([
120 3
                [new TableCell('All surah', array('colspan' => 4))],
121
            ])
122 3
            ->setRows($surah)
123
        ;
124 3
        $table->render();
125 3
    }
126
127 3
    private function array_chunk_vertical($data, $columns)
128
    {
129 3
        $n = count($data);
130 3
        $per_column = floor($n / $columns);
131 3
        $rest = $n % $columns;
132
133
        // The map
134 3
        $per_columns = array();
135 3
        for ($i = 0; $i < $columns; ++$i) {
136 3
            $per_columns[$i] = $per_column + ($i < $rest ? 1 : 0);
137
        }
138
139 3
        $tabular = array();
140 3
        foreach ($per_columns as $rows) {
141 3
            for ($i = 0; $i < $rows; ++$i) {
142 3
                $tabular[$i][ ] = array_shift($data);
143
            }
144
        }
145
146 3
        return $tabular;
147
    }
148
149
    /**
150
     * @param int|string|null $surah
151
     * @param InputInterface  $input
152
     * @param OutputInterface $output
153
     *
154
     * @return int|null
155
     *
156
     * @throws SurahInvalid
157
     */
158 33
    private function confirmSurahNo($surah, $input, $output)
159
    {
160 33
        if ($surah && !is_numeric($surah)) {
161 12
            $surah_list = $this->quran->surah();
162
            $surah_list_array = array_map(function ($surah) { return $surah->tname; }, (array) $surah_list);
163 12
            $closest_surah = $this->closestSurah($surah, $surah_list_array);
164
165 12
            if (sizeof($closest_surah) == 1) {
166 6
                $surah = $closest_surah[0];
167 6
            } elseif (sizeof($closest_surah) > 1) {
168 3
                $surah = $this->askSurah($closest_surah, $input, $output);
169
            } else {
170 3
                throw new SurahInvalid();
171
            }
172 9
            $surah = array_search($surah, $surah_list_array);
173
        }
174
175 30
        return ($surah === null) ? null : intval($surah);
176
    }
177
178
    /**
179
     * @param string $surah
180
     * @param array  $options
181
     *
182
     * @return array
183
     */
184 12
    private function closestSurah($surah, $options)
185
    {
186 12
        $l = new Levenshtein();
187
188 12
        return $l->closest($surah, $options);
189
    }
190
191
    /**
192
     * @param array           $options
193
     * @param InputInterface  $input
194
     * @param OutputInterface $output
195
     *
196
     * @return mixed
197
     */
198 3
    private function askSurah($options, $input, $output)
199
    {
200 3
        $this->setHelperSet(new HelperSet([new QuestionHelper()]));
201 3
        $helper = $this->getHelper('question');
202 3
        $question = new ChoiceQuestion('No surah found. Did you mean one of the following?', $options, 0);
203 3
        $question->setErrorMessage('Surah %s is invalid.');
204
205 3
        return $helper->ask($input, $output, $question);
206
    }
207
208 18
    private function parseResult($args)
209
    {
210
        // Just a single ayah is return. No need to parse anything.
211 18
        if (is_string($args)) {
212 9
            return $args."\n";
213
        }
214
215
        // Multiple ayah/one surah or multiple surah/one ayah. Not both.
216 9
        if (is_string(current($args))) {
217 6
            return $this->buildAyah($args);
218
        }
219
220
        // Both multiple ayah and multiple surah.
221 3
        $count = 0;
222 3
        $result = "\n";
223
224 3
        foreach ($args as $translation => $aya) {
225 3
            $result .= strtoupper($translation)."\n".str_repeat('=', strlen($translation) + 2)."\n\n";
226 3
            $result .= $this->buildAyah($aya);
227
228 3
            ++$count;
229 3
            if ($count < sizeof($args)) {
230 3
                $result .= "\n\n";
231
            }
232
        }
233
234 3
        return $result;
235
    }
236
237 9
    private function buildAyah($ayah)
238
    {
239 9
        $result = '';
240 9
        foreach ($ayah as $num => $aya) {
241 9
            $result .= '[ '.strtoupper($num)." ]\t".$aya."\n";
242
        }
243
244 9
        return $result;
245
    }
246
}
247