support git clones and (PECL) package archives as sources
[pharext/pharext] / src / pharext / Cli / Command.php
1 <?php
2
3 namespace pharext\Cli;
4
5 use pharext\Cli\Args as CliArgs;
6
7 require_once "pharext/Version.php";
8
9 trait Command
10 {
11 /**
12 * Command line arguments
13 * @var pharext\CliArgs
14 */
15 private $args;
16
17 /**
18 * @inheritdoc
19 * @see \pharext\Command::getArgs()
20 */
21 public function getArgs() {
22 return $this->args;
23 }
24
25 /**
26 * Output pharext vX.Y.Z header
27 */
28 function header() {
29 printf("pharext v%s (c) Michael Wallner <mike@php.net>\n\n",
30 \pharext\VERSION);
31 }
32
33 /**
34 * @inheritdoc
35 * @see \pharext\Command::info()
36 */
37 public function info($fmt) {
38 if (!$this->args->quiet) {
39 vprintf($fmt, array_slice(func_get_args(), 1));
40 }
41 }
42
43 /**
44 * @inheritdoc
45 * @see \pharext\Command::error()
46 */
47 public function error($fmt) {
48 if (!$this->args->quiet) {
49 if (!isset($fmt)) {
50 $fmt = "%s\n";
51 $arg = error_get_last()["message"];
52 } else {
53 $arg = array_slice(func_get_args(), 1);
54 }
55 vfprintf(STDERR, "ERROR: $fmt", $arg);
56 }
57 }
58
59 /**
60 * Output command line help message
61 * @param string $prog
62 */
63 public function help($prog) {
64 printf("Usage:\n\n \$ %s", $prog);
65
66 $flags = [];
67 $required = [];
68 $optional = [];
69 foreach ($this->args->getSpec() as $spec) {
70 if ($spec[3] & CliArgs::REQARG) {
71 if ($spec[3] & CliArgs::REQUIRED) {
72 $required[] = $spec;
73 } else {
74 $optional[] = $spec;
75 }
76 } else {
77 $flags[] = $spec;
78 }
79 }
80
81 if ($flags) {
82 printf(" [-%s]", implode("", array_column($flags, 0)));
83 }
84 foreach ($required as $req) {
85 printf(" -%s <arg>", $req[0]);
86 }
87 if ($optional) {
88 printf(" [-%s <arg>]", implode("|-", array_column($optional, 0)));
89 }
90 printf("\n\n");
91 $spc = $this->args->getSpec();
92 $max = max(array_map("strlen", array_column($spc, 1)));
93 $max += $max % 8 + 2;
94 foreach ($spc as $spec) {
95 if (isset($spec[0])) {
96 printf(" -%s|", $spec[0]);
97 } else {
98 printf(" ");
99 }
100 printf("--%s ", $spec[1]);
101 if ($spec[3] & CliArgs::REQARG) {
102 printf("<arg> ");
103 } elseif ($spec[3] & CliArgs::OPTARG) {
104 printf("[<arg>]");
105 } else {
106 printf(" ");
107 }
108 printf("%s%s", str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0])), $spec[2]);
109 if ($spec[3] & CliArgs::REQUIRED) {
110 printf(" (REQUIRED)");
111 }
112 if (isset($spec[4])) {
113 printf(" [%s]", $spec[4]);
114 }
115 printf("\n");
116 }
117 printf("\n");
118 }
119
120 /**
121 * Create temporary file/directory name
122 * @param string $prefix
123 * @param string $suffix
124 */
125 private function tempname($prefix, $suffix = null) {
126 if (!isset($suffix)) {
127 $suffix = uniqid();
128 }
129 return sprintf("%s/%s.%s", sys_get_temp_dir(), $prefix, $suffix);
130 }
131
132 /**
133 * Create a new temp directory
134 * @param string $prefix
135 * @return string
136 */
137 private function newtemp($prefix) {
138 $temp = $this->tempname($prefix);
139 if (!is_dir($temp)) {
140 if (!mkdir($temp, 0700, true)) {
141 $this->error(null);
142 exit(3);
143 }
144 }
145 return $temp;
146 }
147
148 /**
149 * rm -r
150 * @param string $dir
151 */
152 private function rm($dir) {
153 foreach (scandir($dir) as $entry) {
154 if ($entry === "." || $entry === "..") {
155 continue;
156 } elseif (is_dir("$dir/$entry")) {
157 $this->rm("$dir/$entry");
158 } elseif (!unlink("$dir/$entry")) {
159 $this->error(null);
160 }
161 }
162 if (!rmdir($dir)) {
163 $this->error(null);
164 }
165 }
166
167 /**
168 * Execute a program with escalated privileges handling interactive password prompt
169 * @param string $command
170 * @param string $output
171 * @return int
172 */
173 private function sudo($command, &$output) {
174 if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) {
175 return -1;
176 }
177 $stdout = $pipes[1];
178 $passwd = 0;
179 while (!feof($stdout)) {
180 $R = [$stdout]; $W = []; $E = [];
181 if (!stream_select($R, $W, $E, null)) {
182 continue;
183 }
184 $data = fread($stdout, 0x1000);
185 /* only check a few times */
186 if ($passwd++ < 10) {
187 if (stristr($data, "password")) {
188 printf("\n%s", $data);
189 }
190 }
191 $output .= $data;
192 }
193 return proc_close($proc);
194 }
195
196 /**
197 * Execute a system command
198 * @param string $name pretty name
199 * @param string $command command
200 * @param array $args command arguments
201 * @param bool $sudo whether the command may need escalated privileges
202 */
203 private function exec($name, $command, array $args = null, $sudo = false) {
204 $exec = escapeshellcmd($command);
205 if ($args) {
206 $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args));
207 }
208
209 if ($this->args->verbose) {
210 $this->info("Running %s ...\n", $exec);
211 } else {
212 $this->info("Running %s ... ", $name);
213 }
214
215 if ($sudo && isset($this->args->sudo)) {
216 $retval = $this->sudo(sprintf($this->args->sudo." 2>&1", $exec), $output);
217 } elseif ($this->args->verbose) {
218 passthru($exec ." 2>&1", $retval);
219 } else {
220 exec($exec ." 2>&1", $output, $retval);
221 $output = implode("\n", $output);
222 }
223
224 if ($retval) {
225 $this->error("Command %s failed with (%s)\n", $command, $retval);
226 if (isset($output) && !$this->args->quiet) {
227 printf("%s\n", $output);
228 }
229 exit(2);
230 }
231 if (!$this->args->verbose) {
232 // we already have a bunch of output
233 $this->info("OK\n");
234 }
235 }
236 }