1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of graze/data-file |
4
|
|
|
* |
5
|
|
|
* Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
* |
10
|
|
|
* @license https://github.com/graze/data-file/blob/master/LICENSE.md |
11
|
|
|
* @link https://github.com/graze/data-file |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace Graze\DataFile\Modify; |
15
|
|
|
|
16
|
|
|
use Graze\DataFile\Helper\GetOptionTrait; |
17
|
|
|
use Graze\DataFile\Helper\OptionalLoggerTrait; |
18
|
|
|
use Graze\DataFile\Helper\Process\ProcessFactoryAwareInterface; |
19
|
|
|
use Graze\DataFile\Node\FileNodeInterface; |
20
|
|
|
use Graze\DataFile\Node\LocalFile; |
21
|
|
|
use InvalidArgumentException; |
22
|
|
|
use Psr\Log\LoggerAwareInterface; |
23
|
|
|
use Psr\Log\LogLevel; |
24
|
|
|
use Symfony\Component\Process\Exception\ProcessFailedException; |
25
|
|
|
|
26
|
|
|
class ReplaceText implements FileModifierInterface, LoggerAwareInterface, ProcessFactoryAwareInterface |
27
|
|
|
{ |
28
|
|
|
use OptionalLoggerTrait; |
29
|
|
|
use FileProcessTrait; |
30
|
|
|
use GetOptionTrait; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Can this file be modified by this modifier |
34
|
|
|
* |
35
|
|
|
* @param FileNodeInterface $file |
36
|
|
|
* |
37
|
|
|
* @return bool |
38
|
|
|
*/ |
39
|
1 |
|
public function canModify(FileNodeInterface $file) |
40
|
|
|
{ |
41
|
|
|
return ( |
42
|
1 |
|
($file instanceof localFile) && |
43
|
1 |
|
($file->exists()) |
44
|
1 |
|
); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Modify the file |
49
|
|
|
* |
50
|
|
|
* @param FileNodeInterface $file |
51
|
|
|
* @param array $options List of options: |
52
|
|
|
* -fromText <string|array> Text to be replace |
53
|
|
|
* -toText <string|array> Text to replace |
54
|
|
|
* -postifx <string> (Default: replace) Set this to blank to replace inline |
55
|
|
|
* -keepOldFile <bool> (Default: true) |
56
|
|
|
* |
57
|
|
|
* @return FileNodeInterface |
58
|
|
|
*/ |
59
|
5 |
|
public function modify(FileNodeInterface $file, array $options = []) |
60
|
|
|
{ |
61
|
5 |
|
$this->options = $options; |
62
|
5 |
|
$fromText = $this->requireOption('fromText'); |
63
|
4 |
|
$toText = $this->requireOption('toText'); |
64
|
|
|
|
65
|
3 |
|
unset($options['fromText']); |
66
|
3 |
|
unset($options['toText']); |
67
|
|
|
|
68
|
3 |
|
if (!($file instanceof LocalFile)) { |
69
|
1 |
|
throw new InvalidArgumentException("Supplied: $file is not a LocalFile"); |
70
|
|
|
} |
71
|
|
|
|
72
|
2 |
|
return $this->replaceText($file, $fromText, $toText, $options); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @extend Graze\DataFile\Node\File\LocalFile |
77
|
|
|
* |
78
|
|
|
* @param LocalFile $file |
79
|
|
|
* @param string|string[] $fromText |
80
|
|
|
* @param string|string[] $toText |
81
|
|
|
* @param array $options List of options: |
82
|
|
|
* -postfix <string> (Default: replace) Set this to blank to replace inline |
83
|
|
|
* -keepOldFile <bool> (Default: true) |
84
|
|
|
* |
85
|
|
|
* @throws InvalidArgumentException |
86
|
|
|
* @throws ProcessFailedException |
87
|
|
|
* @return LocalFile |
88
|
|
|
*/ |
89
|
11 |
|
public function replaceText(LocalFile $file, $fromText, $toText, array $options = []) |
90
|
|
|
{ |
91
|
11 |
|
$this->options = $options; |
92
|
|
|
|
93
|
11 |
|
$postfix = $this->getOption('postfix', 'replace'); |
94
|
11 |
|
$output = $this->getTargetFile($file, $postfix); |
95
|
|
|
|
96
|
11 |
|
if (is_array($fromText)) { |
97
|
3 |
|
if (is_array($toText) && |
98
|
3 |
|
count($fromText) == count($toText) |
99
|
3 |
|
) { |
100
|
2 |
|
$sedStrings = []; |
101
|
2 |
|
$fromSize = count($fromText); |
102
|
2 |
|
for ($i = 0; $i < $fromSize; $i++) { |
103
|
2 |
|
$sedStrings[] = $this->getReplacementCommand($fromText[$i], $toText[$i]); |
104
|
2 |
|
} |
105
|
2 |
|
$replacementString = implode(';', $sedStrings); |
106
|
2 |
|
} else { |
107
|
1 |
|
throw new InvalidArgumentException("Number of items in 'fromText' (" . count($fromText) . ") is different to 'toText' (" . count($toText) . ")"); |
108
|
|
|
} |
109
|
2 |
|
} else { |
110
|
8 |
|
$replacementString = $this->getReplacementCommand($fromText, $toText); |
|
|
|
|
111
|
|
|
} |
112
|
|
|
|
113
|
10 |
|
if ($file->getFilename() == $output->getFilename()) { |
114
|
1 |
|
$cmd = sprintf( |
115
|
1 |
|
"perl -p -i -e '%s' %s", |
116
|
1 |
|
$replacementString, |
117
|
1 |
|
$file->getPath() |
118
|
1 |
|
); |
119
|
1 |
|
} else { |
120
|
9 |
|
$cmd = sprintf( |
121
|
9 |
|
"perl -p -e '%s' %s > %s", |
122
|
9 |
|
$replacementString, |
123
|
9 |
|
$file->getPath(), |
124
|
9 |
|
$output->getPath() |
125
|
9 |
|
); |
126
|
|
|
} |
127
|
|
|
|
128
|
10 |
|
$this->log(LogLevel::INFO, "Replacing the text: {from} to {to} in file: '{file}'", [ |
129
|
10 |
|
'from' => json_encode($fromText), |
130
|
10 |
|
'to' => json_encode($toText), |
131
|
10 |
|
'file' => $file, |
132
|
10 |
|
]); |
133
|
|
|
|
134
|
10 |
|
return $this->processFile($file, $output, $cmd, $this->getOption('keepOldFile', true)); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Get the string replacement command for a single item |
139
|
|
|
* |
140
|
|
|
* @param string $fromText |
141
|
|
|
* @param string $toText |
142
|
|
|
* |
143
|
|
|
* @return string |
144
|
|
|
*/ |
145
|
10 |
|
private function getReplacementCommand($fromText, $toText) |
146
|
|
|
{ |
147
|
10 |
|
return sprintf( |
148
|
10 |
|
's/%s/%s/g', |
149
|
10 |
|
str_replace(["'", ";", "\\"], ["\\'", "\\;", "\\\\"], $fromText), |
150
|
10 |
|
str_replace(["'", ";", "\\"], ["\\'", "\\;", "\\\\"], $toText) |
151
|
10 |
|
); |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.