1: <?php
2: mklog(1,'Loading packages');
3: require_once 'packages.php';
4:
5: mklog(1,'Took ' . round(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], 3) . ' seconds to start');
6:
7: cli::start();
8:
9: /**
10: * The main program and command line.
11: */
12: class cli{
13: private static $started = false;
14: private static $aliases = [];
15: /**
16: * @internal
17: */
18: public static function command(string $line):void{
19: $lines = explode(" ", $line);
20: if($lines[0] === "new"){
21: cmd::newWindow(cmd::startScript() . " " . substr($line,4));
22: }
23: elseif($lines[0] === "reload"){
24: $cmd = cmd::startScript() . " " . argsString();
25: if(getEnvironment()['desktop']){
26: cmd::newWindow($cmd);
27: exit;
28: }
29: else{
30: exit(5);//exit code 5 signals the start script to restart php
31: }
32: }
33: elseif($lines[0] === "clear"){
34: cli_formatter::clear();
35: }
36: else{
37: echo "Commands: new [args], reload, clear\n";
38: }
39: }
40:
41: /**
42: * @internal
43: */
44: public static function start(){
45: if(self::$started){
46: return;
47: }
48: self::$started = true;
49:
50: while(true){
51: foreach(self::getCommands() as $command){
52: self::run($command);
53: }
54: self::after();
55: }
56: }
57: /**
58: * Runs a command.
59: *
60: * @param string $line The command to run.
61: * @param boolean $captureOutput Weather to return the recorded echo's or not.
62: * @return boolean|string If captureOutput is false, a boolean indicating succcess will be returned, otherwise a string will be returned.
63: */
64: public static function run(string $line, bool $captureOutput=false):bool|string{
65: $return = false;
66: if($line === "exit"){
67: mklog(0, 'Exiting');
68: exit;
69: }
70:
71: if(strpos($line," ") !== false){
72: $spacePos = strpos($line," ");
73: $baseCommand = substr($line,0,$spacePos);
74: $line = substr($line,$spacePos+1);
75: }
76: else{
77: $baseCommand = $line;
78: $line = "";
79: }
80: $baseCommand = strtolower($baseCommand);
81:
82: if(isset(self::$aliases[$baseCommand])){
83: return self::run(self::$aliases[$baseCommand] . " " . $line, $captureOutput);
84: }
85:
86: if($captureOutput){
87: mklog(0, 'Breifly capturing output');
88: ob_start();
89: }
90:
91: if(class_exists($baseCommand)){
92: if(method_exists($baseCommand,'command')){
93: $return = true;
94: try{
95: $baseCommand::command($line);
96: }
97: catch(Throwable $throwable){
98: mklog(3,"Something went wrong while trying to run: " . $baseCommand . " " . $line . " (" . substr($throwable,0,strpos($throwable,"\n")) . ")");
99: $return = false;
100: }
101: }
102: else{
103: echo "The package " . $baseCommand . " does not have a command function.\n";
104: }
105: }
106: else{
107: echo "Unknown package or command: " . $baseCommand . "\n";
108: }
109:
110: if($captureOutput){
111: return ob_get_clean();
112: }
113:
114: return $return;
115: }
116: /**
117: * Registers a command alias, this allows you to map a command to a longer one.
118: *
119: * @param string $alias The nickname for the command.
120: * @param string $command The command the alias gets replaced with.
121: * @return boolean Indicates success.
122: */
123: public static function registerAlias(string $alias, string $command):bool{
124: if(class_exists($alias) || isset(self::$aliases[$alias]) || $alias === "exit"){
125: return false;
126: }
127:
128: if(verboseLogging()){
129: $class = false;
130: $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
131: if(isset($trace[1]['class'])){
132: $class = $trace[1]['class'];
133: }
134:
135: mklog(0, ($class ? $class . ' r' : 'R') . 'egistered the alias "' . $alias . '" for the command "' . $command . '"');
136: }
137:
138: self::$aliases[$alias] = $command;
139: return true;
140: }
141:
142: /**
143: * Returns the version number of PHP-CLI.
144: *
145: * @return integer The current PHP-CLI version number.
146: */
147: public static function version():int{
148: return 107;
149: }
150: /**
151: * Gets general info about the PHP-CLI install and some system information.
152: *
153: * @return array See readme.
154: */
155: public static function info():array{
156: return [
157: 'version' => self::version(),
158: 'startTime' => $_SERVER['REQUEST_TIME_FLOAT'],
159: 'pcName' => gethostname(),
160: 'cpuThreads' => (PHP_OS_FAMILY === 'Linux' ? (int) shell_exec('nproc') : $_SERVER['NUMBER_OF_PROCESSORS']),
161: 'phpVersionNumeric' => PHP_VERSION_ID,
162: 'phpVersion' => phpversion(),
163: 'osType' => PHP_OS_FAMILY,
164: 'osVersion' => php_uname('r'),
165: 'osBuild' => php_uname('v')
166: ];
167: }
168: /**
169: * Parses a command string into arguments, options and parameters.
170: * Inputted "words" can be quoted with double-quotes to stop spaces making it 2 words.
171: * Arguments are strings on their own, options are preceded with a "-" and are added to the options list if they exist,
172: * parameters are preceded with "--" and then take watever was behind them as the value, this is added to the params array and is keyed with the parameter name and has the parameter string value.
173: *
174: * @param string $line The line containing words.
175: * @return array The args, options, and params arrays.
176: */
177: public static function parseLine(string $line):array{
178: $words = str_getcsv($line, ' ', '"', '\\');
179:
180: $return = [
181: "args" => [],
182: "options" => [],
183: "params" => []
184: ];
185:
186: $param = null;
187: foreach($words as $word){
188: $word = trim($word);
189:
190: if($param){
191: $return["params"][$param] = $word;
192: $param = null;
193: continue;
194: }
195:
196: if(substr($word, 0, 2) === "--" && strlen($word) > 2){
197: $param = strtolower(substr($word, 2));
198: continue;
199: }
200: if(substr($word, 0, 1) === "-" && strlen($word) > 1){
201: $return["options"][] = strtolower(substr($word, 1));
202: continue;
203: }
204:
205: $return["args"][] = $word;
206: }
207:
208: return $return;
209: }
210:
211: private static function getCommands():array{
212: global $arguments;
213:
214: if(is_string($arguments['command'])){
215: mklog(0, 'Running argument command');
216: $line = $arguments['command'];
217: }
218: elseif(is_string($arguments['use-file-as-input'])){
219: if(!is_file($arguments['use-file-as-input'])){
220: return [];
221: }
222:
223: mklog(0, 'Running command from the file ' . $arguments['use-file-as-input']);
224:
225: $line = file_get_contents($arguments['use-file-as-input']);
226:
227: if(!is_string($line) || empty($line)){
228: mklog(2,'Failed to read command from ' . $arguments['use-file-as-input']);
229: return [];
230: }
231: }
232: else{
233: $line = user_input::await();
234: }
235:
236: return explode(" && ", $line);
237: }
238: private static function after(){
239: global $arguments;
240:
241: if($arguments['no-loop'] === true){
242: exit;
243: }
244:
245: if(is_string($arguments['command'])){
246: $arguments['command'] = false;
247: }
248:
249: if(is_string($arguments['use-file-as-input'])){
250: if(is_file($arguments['use-file-as-input'])){
251: if(!unlink($arguments['use-file-as-input'])){
252: mklog(3,'Failed to delete executed command file ' . $arguments['use-file-as-input']);
253: $arguments['use-file-as-input'] = false;
254: }
255: }
256: sleep($arguments['file-as-input-delay']);
257: }
258: }
259: }