CsvFolderJoinFiles   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 69
c 4
b 0
f 0
dl 0
loc 158
ccs 84
cts 84
cp 1
rs 9.92
wmc 31

11 Methods

Rating   Name   Duplication   Size   Complexity  
A splReadTenLines() 0 12 3
A joinFilesInFolder() 0 6 2
A findLinesToSkipFromIterators() 0 13 5
A findLinesToSkip() 0 6 1
A joinFilesToDestination() 0 14 5
A lastLineContains() 0 10 3
A splCurrentLinesNormalizeValue() 0 5 1
A writeLines() 0 9 1
B obtainFilesThatAreSplitted() 0 32 6
A obtainFileLastLine() 0 4 2
A compareFiles() 0 3 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpCfdi\SatCatalogosPopulate\Converters;
6
7
use Iterator;
8
use SplFileObject;
9
10
use function PhpCfdi\SatCatalogosPopulate\Utils\array_rtrim;
11
12
class CsvFolderJoinFiles
13
{
14 4
    public function joinFilesInFolder(string $csvFolder): void
15
    {
16 4
        $destinations = $this->obtainFilesThatAreSplitted($csvFolder);
17
18 4
        foreach ($destinations as $destination => $files) {
19 4
            $this->joinFilesToDestination(array_values($files), $destination);
20
        }
21
    }
22
23
    /**
24
     * @param array<int, string> $files
25
     */
26 5
    public function joinFilesToDestination(array $files, string $destination): void
27
    {
28 5
        $skipFirstLines = 0;
29 5
        $firstSource = $files[0];
30 5
        file_put_contents($destination, ''); // clear the file contents
31 5
        foreach ($files as $i => $source) {
32 5
            if ($i > 0 && 0 === $skipFirstLines) {
33 5
                $skipFirstLines = $this->findLinesToSkip($firstSource, $source);
34
            }
35 5
            $skipLastLines = 0;
36 5
            if ($this->lastLineContains($source, ['Continúa en'])) {
37 5
                $skipLastLines = 1;
38
            }
39 5
            $this->writeLines($source, $destination, $skipFirstLines, $skipLastLines);
40
        }
41
    }
42
43
    /**
44
     * @return array<string, array<int, string>>
45
     */
46 6
    public function obtainFilesThatAreSplitted(string $csvFolder): array
47
    {
48 6
        $files = array_filter(
49 6
            array_map(
50 6
                function ($path): array {
51 6
                    $file = basename($path);
52 6
                    $matches = [];
53
                    if (
54 6
                        ! preg_match('/^ *(.+)_Parte_([0-9]+) *\.csv$/', $file, $matches)
55 6
                        && ! preg_match('/^ *(.+) \(Parte ([0-9]+)\) *\.csv$/', $file, $matches)
56 6
                        && ! preg_match('/^ *(.+)_([0-9]+) *\.csv$/', $file, $matches)
57
                    ) {
58 4
                        return [];
59
                    }
60 6
                    return [
61 6
                        'destination' => dirname($path) . '/' . trim($matches[1]) . '.csv',
62 6
                        'index' => (int) $matches[2],
63 6
                        'source' => $path,
64 6
                    ];
65 6
                },
66 6
                glob($csvFolder . '/*.csv') ?: []
67 6
            )
68 6
        );
69
70 6
        uasort($files, $this->compareFiles(...));
71
72 6
        $destinations = [];
73 6
        foreach ($files as $file) {
74 6
            $destinations[strval($file['destination'])][] = strval($file['source']);
75
        }
76
77 6
        return $destinations;
78
    }
79
80
    /**
81
     * @param array{destination: string, index: int} $first
82
     * @param array{destination: string, index: int} $second
83
     */
84 6
    private function compareFiles(array $first, array $second): int
0 ignored issues
show
Unused Code introduced by
The method compareFiles() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
85
    {
86 6
        return ($first['destination'] <=> $second['destination']) ?: $first['index'] <=> $second['index'];
87
    }
88
89 5
    public function writeLines(string $source, string $destination, int $skipFirstLines, int $skipLastLines): void
90
    {
91 5
        $command = implode(' ', [
92 5
            'cat ' . escapeshellarg($source),                   // send the file to the pipes
93 5
            sprintf('| tail -n +%d', $skipFirstLines + 1),      // without firsts n lines
94 5
            sprintf('| head -n -%d', $skipLastLines),           // without last n lines
95 5
            '>> ' . escapeshellarg($destination),               // create/append to destination
96 5
        ]);
97 5
        shell_exec($command);
98
    }
99
100 6
    public function findLinesToSkip(string $firstPath, string $secondPath): int
101
    {
102 6
        $first = new SplFileObject($firstPath, 'r');
103 6
        $second = new SplFileObject($secondPath, 'r');
104
105 6
        return $this->findLinesToSkipFromIterators($first, $second);
106
    }
107
108
    /**
109
     * @param Iterator<string> $first
110
     * @param Iterator<string> $second
111
     */
112 10
    public function findLinesToSkipFromIterators(Iterator $first, Iterator $second): int
113
    {
114 10
        $firstTenLines = $this->splReadTenLines($first);
115 10
        $secondTenLines = $this->splReadTenLines($second);
116
117 10
        for ($i = 9; $i > 0; $i--) {
118 10
            $firstValue = $firstTenLines[$i] ?? null;
119 10
            $secondValue = $secondTenLines[$i] ?? null;
120 10
            if (null !== $firstValue && null !== $secondValue && $firstValue === $secondValue) {
121 10
                return $i + 1;
122
            }
123
        }
124 1
        return 0;
125
    }
126
127
    /**
128
     * @param Iterator<string> $iterator
129
     * @return array<int, string>
130
     */
131 10
    public function splReadTenLines(Iterator $iterator): array
132
    {
133 10
        $result = [];
134 10
        $counter = 0;
135 10
        foreach ($iterator as $line) {
136 10
            $result[] = $this->splCurrentLinesNormalizeValue($line);
137 10
            $counter = $counter + 1;
138 10
            if (10 === $counter) {
139 1
                break;
140
            }
141
        }
142 10
        return $result;
143
    }
144
145 10
    private function splCurrentLinesNormalizeValue(string $current): string
146
    {
147
        // explode values, trim values, remove empty values at end and implode values back
148 10
        return implode(',', array_rtrim(
149 10
            array_map(static fn (?string $value): string => trim($value ?? ''), str_getcsv($current))
150 10
        ));
151
    }
152
153
    /** @param string[] $searchterms */
154 5
    public function lastLineContains(string $filename, array $searchterms): bool
155
    {
156 5
        $lastline = $this->obtainFileLastLine($filename);
157 5
        foreach ($searchterms as $search) {
158 5
            if (str_contains($lastline, $search)) {
159 5
                return true;
160
            }
161
        }
162
163 5
        return false;
164
    }
165
166 5
    public function obtainFileLastLine(string $filename): string
167
    {
168 5
        $command = sprintf("tail -n 5 %s | grep -v '/^$/' | tail -n 1", escapeshellarg($filename));
169 5
        return shell_exec($command) ?: '';
170
    }
171
}
172