better help for positional args
[pharext/pharext] / src / pharext / Cli / Args / Help.php
1 <?php
2
3 namespace pharext\Cli\Args;
4
5 use pharext\Cli\Args;
6
7 class Help
8 {
9 private $args;
10
11 function __construct($prog, Args $args) {
12 $this->prog = $prog;
13 $this->args = $args;
14 }
15
16 function __toString() {
17 $usage = "Usage:\n\n \$ ";
18 $usage .= $this->prog;
19
20 list($flags, $required, $optional, $positional) = $this->listSpec();
21 if ($flags) {
22 $usage .= $this->dumpFlags($flags);
23 }
24 if ($required) {
25 $usage .= $this->dumpRequired($required);
26 }
27 if ($optional) {
28 $usage .= $this->dumpOptional($optional);
29 }
30 if ($positional) {
31 $usage .= $this->dumpPositional($positional);
32 }
33
34 $help = $this->dumpHelp($positional);
35
36 return $usage . "\n\n" . $help . "\n";
37 }
38
39 function listSpec() {
40 $flags = [];
41 $required = [];
42 $optional = [];
43 $positional = [];
44 foreach ($this->args->getSpec() as $spec) {
45 if (is_numeric($spec[0])) {
46 $positional[] = $spec;
47 } elseif ($spec[3] & Args::REQUIRED) {
48 $required[] = $spec;
49 } elseif ($spec[3] & (Args::OPTARG|Args::REQARG)) {
50 $optional[] = $spec;
51 } else {
52 $flags[] = $spec;
53 }
54 }
55
56 return [$flags, $required, $optional, $positional]
57 + compact("flags", "required", "optional", "positional");
58 }
59
60 function dumpFlags(array $flags) {
61 return sprintf(" [-%s]", implode("", array_column($flags, 0)));
62 }
63
64 function dumpRequired(array $required) {
65 $dump = "";
66 foreach ($required as $req) {
67 $dump .= sprintf(" -%s <%s>", $req[0], $req[1]);
68 }
69 return $dump;
70 }
71
72 function dumpOptional(array $optional) {
73 $req = array_filter($optional, function($a) {
74 return $a[3] & Args::REQARG;
75 });
76 $opt = array_filter($optional, function($a) {
77 return $a[3] & Args::OPTARG;
78 });
79
80 $dump = "";
81 if ($req) {
82 $dump .= sprintf(" [-%s <arg>]", implode("|-", array_column($req, 0)));
83 }
84 if ($opt) {
85 $dump .= sprintf(" [-%s [<arg>]]", implode("|-", array_column($opt, 0)));
86 }
87 return $dump;
88 }
89
90 function dumpPositional(array $positional) {
91 $dump = " [--]";
92 $conv = [];
93 foreach ($positional as $pos) {
94 $conv[$pos[0]][] = $pos;
95 }
96 $opts = [];
97 foreach ($conv as $positional) {
98 $args = implode("|", array_column($positional, 1));
99 if ($positional[0][3] & Args::REQUIRED) {
100 $dump .= sprintf(" <%s>", $args);
101 } else {
102 $dump .= sprintf(" [<%s>]", $args);
103 }
104 if ($positional[0][3] & Args::MULTI) {
105 $dump .= sprintf(" [<%s>]...", $args);
106 }
107 /*
108 foreach ($positional as $pos) {
109 if ($pos[3] & Args::REQUIRED) {
110 $dump .= sprintf(" <%s>", $pos[1]);
111 } else {
112 $opts[] = $pos;
113 //$dump .= sprintf(" [<%s>]", $pos[1]);
114 }
115 if ($pos[3] & Args::MULTI) {
116 $dump .= sprintf(" [<%s>]...", $pos[1]);
117 }
118 }
119 */
120 }
121 return $dump;
122 }
123
124 function calcMaxLen() {
125 $spc = $this->args->getSpec();
126 $max = max(array_map("strlen", array_column($spc, 1)));
127 $max += $max % 8 + 2;
128 return $max;
129 }
130
131 function dumpHelp() {
132 $max = $this->calcMaxLen();
133 $dump = "";
134 foreach ($this->args->getSpec() as $spec) {
135 $dump .= " ";
136 if (is_numeric($spec[0])) {
137 $dump .= sprintf("-- %s ", $spec[1]);
138 } elseif (isset($spec[0])) {
139 $dump .= sprintf("-%s|", $spec[0]);
140 }
141 if (!is_numeric($spec[0])) {
142 $dump .= sprintf("--%s ", $spec[1]);
143 }
144 if ($spec[3] & Args::REQARG) {
145 $dump .= "<arg> ";
146 } elseif ($spec[3] & Args::OPTARG) {
147 $dump .= "[<arg>]";
148 } else {
149 $dump .= " ";
150 }
151
152 $space = str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0]));
153 $dump .= $space;
154 $dump .= str_replace("\n", "\n $space", $spec[2]);
155
156 if ($spec[3] & Args::REQUIRED) {
157 $dump .= " (REQUIRED)";
158 }
159 if ($spec[3] & Args::MULTI) {
160 $dump .= " (MULTIPLE)";
161 }
162 if (isset($spec[4])) {
163 $dump .= sprintf(" [%s]", $spec[4]);
164 }
165 $dump .= "\n";
166 }
167 return $dump;
168 }
169 }