CLI::writeln()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CLI
5
 *
6
 * CLI command routing.
7
 *
8
 * @package core
9
 * @author [email protected]
10
 * @copyright Caffeina srl - 2015 - http://caffeina.com
11
 */
12
13
class CLI {
14
    use Module, Events;
15
16
    protected static $file         = null;
17
    protected static $arguments    = [];
18
    protected static $options      = [];
19
    protected static $commands     = [];
20
    protected static $help         = null;
21
    protected static $error        = null;
22
23
    protected static $shell_colors = [
24
      'BLACK'     =>"\033[0;30m",      'DARKGRAY'     =>"\033[1;30m",
25
      'BLUE'      =>"\033[0;34m",      'LIGHTBLUE'    =>"\033[1;34m",
26
      'GREEN'     =>"\033[0;32m",      'LIGHTGREEN'   =>"\033[1;32m",
27
      'CYAN'      =>"\033[0;36m",      'LIGHTCYAN'    =>"\033[1;36m",
28
      'RED'       =>"\033[0;31m",      'LIGHTRED'     =>"\033[1;31m",
29
      'PURPLE'    =>"\033[0;35m",      'LIGHTPURPLE'  =>"\033[1;35m",
30
      'BROWN'     =>"\033[0;33m",      'YELLOW'       =>"\033[1;33m",
31
      'LIGHTGRAY' =>"\033[0;37m",      'WHITE'        =>"\033[1;37m",
32
      'NORMAL'    =>"\033[0;37m",      'B'            =>"\033[1m",
33
      'ERROR'     =>"\033[1;31m",      'INFO'         =>"\033[0;36m",
34
      'I'         =>"\033[0;30;104m",  'IB'           =>"\033[1;30;104m",
35
      'U'         =>"\033[4m",         'D'            =>"\033[2m",
36
    ];
37
    protected static $color_stack = ['NORMAL'];
38
39
40
    /**
41
     * Bind a callback to a command route
42
     * @param  string   $command  The command route, use ":" before a parameter for extraction.
43
     * @param  callable $callback The callback to be binded to the route.
44
     */
45
    public static function on($command,callable $callback,$description=''){
46
      $parts = preg_split('/\s+/',$command);
47
      static::$commands[array_shift($parts)] = [$parts,$callback,$description];
48
    }
49
50
    /**
51
     * Bind a callback to the "help" route.
52
     * @param  callable $callback The callback to be binded to the route. If omitted triggers the callback.
53
     */
54
    public static function help(callable $callback = null){
55
        $callback
56
          ? is_callable($callback) && static::$help = $callback
57
          : static::$help && call_user_func(static::$help);
58
    }
59
60
    /**
61
     * Bind a callback when an error occurs.
62
     * @param  callable $callback The callback to be binded to the route. If omitted triggers the callback.
63
     */
64
    public static function error(callable $callback = null){
65
        $callback
66
          ? is_callable($callback) && static::$error = $callback
67
          : static::$error && call_user_func(static::$error);
68
    }
69
70
    /**
71
     * Returns the script name.
72
     * @return string
73
     */
74
    public static function name(){
75
        return static::$file;
76
    }
77
78
    /**
79
     * Triggers an error and exit
80
     * @param string $message
81
     */
82
    protected static function triggerError($message){
83
        is_callable(static::$error) && call_user_func(static::$error,$message);
84
        exit -1;
85
    }
86
87
    /**
88
     * Get a passed option
89
     * @param string $key The name of the option paramenter
90
     * @param mixed $default The default value if parameter is omitted. (if a callable it will be evaluated)
91
     * @return mixed
92
     */
93
    public static function input($key=null,$default=null){
94
      return $key ? (isset(static::$options[$key]) ? static::$options[$key] : (is_callable($default)?call_user_func($default):$default)) : static::$options;
95
    }
96
97
    /**
98
     * Returns an explanation for the supported commands
99
     *
100
     * @method commands
101
     *
102
     * @return array   The commands and their description.
103
     */
104
    public static function commands(){
105
       $results = [];
106
       foreach(static::$commands as $name => $cmd){
107
          $results[] = [
108
            'name'        => $name,
109
            'params'      => preg_replace('/:(\w+)/','[$1]',implode(' ',$cmd[0])),
110
            'description' => $cmd[2],
111
          ];
112
       }
113
       return $results;
114
    }
115
116
    /**
117
     * Dispatch the router
118
     * @param  string[] $args The arguments array.
119
     * @return boolean  True if route was correctly dispatched.
120
     */
121
    public static function run($args=null){
122
      if (PHP_SAPI != 'cli') return false;
123
      if($args) {
124
        $_args = $args;
125
        static::$file = basename(isset($_SERVER['PHP_SELF'])?$_SERVER['PHP_SELF']:__FILE__);
126
       } else {
127
        $_args = $_SERVER['argv'];
128
        static::$file = basename(array_shift($_args));
129
      }
130
      foreach($_args as $e) if(strpos($e,'-')===0) {
131
        $h = explode('=',$e);
132
        static::$options[ltrim(current($h),'-')] = isset($h[1])?$h[1]:true;
133
      } else {
134
        static::$arguments[] = $e;
135
      }
136
137
      if(isset(static::$arguments[0])){
138
        $command = array_shift(static::$arguments);
139
        if (empty(static::$commands[$command]))
140
          return static::triggerError("Unknown command [".$command."].");
141
        $cmd = static::$commands[$command];
142
        $pars_vector = [];
143
        foreach ($cmd[0] as $_idx => $segment) {
144
          if ($segment[0]==':'){
145
            // Extract paramenter
146 View Code Duplication
            if (isset(static::$arguments[$_idx])){
147
               $pars_vector[] = static::$arguments[$_idx];
148
            } else return static::triggerError("Command [".$command."] needs more parameters");
149 View Code Duplication
          } else {
150
            // Match command
151
            if (empty(static::$arguments[$_idx]) || $segment!=static::$arguments[$_idx])
152
              return static::triggerError("Command [".$command."] is incomplete.");
153
          }
154
        }
155
        $returns = call_user_func_array($cmd[1], $pars_vector);
156
        echo is_scalar($returns) ? "$returns" : json_encode($returns, JSON_PRETTY_PRINT);
157
        return true;
158
      } else {
159
        static::help();
160
        return false;
161
      }
162
    }
163
164
   /**
165
    * Prints a message to the console with color formatting.
166
    * @param  string $message The html-like encoded message
167
    * @return void
168
    */
169
   public static function write($message){
170
      if( preg_match('~<[^>]+>~',$message)) {
171
         // Use preg_replace_callback for fast regex matches navigation
172
         echo strtr(preg_replace_callback('~^(.*)<([^>]+)>(.+)</\2>(.*)$~USm',function($m){
173
            static::write($m[1]);
174
            $color = strtoupper(trim($m[2]));
175
            if( isset(static::$shell_colors[$color]) ) echo static::$shell_colors[$color];
176
            static::$color_stack[] = $color;
177
            static::write($m[3]);
178
            array_pop(static::$color_stack);
179
            $back_color = array_pop(static::$color_stack) ?: static::$color_stack[]='NORMAL';
180
            if( isset(static::$shell_colors[$back_color]) ) echo static::$shell_colors[$back_color];
181
            static::write($m[4]);
182
         },strtr($message,["\n"=>"&BR;"])),["&BR;"=>PHP_EOL]);
183
      } else {
184
         echo strtr($message,["&BR;"=>PHP_EOL]);
185
      }
186
   }
187
188
   /**
189
    * Like CLI::write, but appends a newline at the end.
190
    * @param  string $message The html-like encoded message
191
    * @return void
192
    */
193
   public static function writeln($message){
194
       static::write($message . PHP_EOL);
195
   }
196
197
   /**
198
    * Set output ANSI color
199
    * @param string $color The color name constant.
200
    * @return void
201
    */
202
   public static function color($color){
203
       if ( isset(static::$shell_colors[$color]) ) echo static::$shell_colors[$color];
204
   }
205
206
   /**
207
    * Edit a temporary block of text with $EDITOR (or nano as fallback)
208
    * @param string $text The initial text of the document.
209
    * @param string $filename The (fake) filename passed to the editor (for syntax highlighting hint).
210
    * @return string The edited contents
211
    */
212
   public static function edit($text,$filename=''){
213
      $EDITOR = getenv('EDITOR')?:'nano';
214
      $tmp = tempnam(sys_get_temp_dir(), "E-").strtr($filename,'/','_');
215
      file_put_contents($tmp, $text);
216
      passthru("$EDITOR $tmp");
217
      $result = file_get_contents($tmp);
218
      unlink($tmp);
219
      return $result;
220
  }
221
222
223
}
224
225
226
// Standard Help Message
227
CLI::help(function(){
228
  echo 'Usage: ', CLI::name(),' [commands]', PHP_EOL,
229
       'Commands:',PHP_EOL;
230
  foreach( CLI::commands() as $cmd ){
231
    echo "\t", $cmd['name'], ' ' ,$cmd['params'], PHP_EOL;
232
    if($cmd['description'])
233
      echo "\t\t- ", str_replace("\n","\n\t\t  ",$cmd['description']), PHP_EOL, PHP_EOL;
234
  }
235
});
236
237
// Standard Error Message
238
CLI::error(function($message){
239
  echo 'Error: ',$message,PHP_EOL;
240
});
241