Commits

codemole committed dba5624

Implemented somewhat more sophisticated command-line option parsing

  • Participants
  • Parent commits 8b3c723

Comments (0)

Files changed (2)

File lib/GetOpt.class.php

+<?php
+
+class GetOpt {
+  static protected $default_config = array(
+    'optVal'
+  );
+  private static $errors = array();
+  
+  /**
+   * Parse and extract left-most options up to the first non-option argument
+   *
+   * @param array $args List of arguments to search through
+   * @param array $opts Option templates. Defines rules for the options we need to parse
+   * @return array Extracted options
+   */
+  static function extractLeft(&$args, $opts)
+  {
+    $result = array();
+    self::$errors = array();
+    $opts = self::normalizeTpl($opts);
+    $short_opts = self::mapShortOpts($opts);
+    
+    while(!empty($args))
+    {
+      $arg = array_shift($args);
+      if(preg_match('/^--([a-z][a-z\-]*)/i', $arg, $matches)) //long options start with "--"
+      {
+        $matches[1] = strtolower($matches[1]);
+        if(isset($opts[$matches[1]]))
+        {
+          try
+          {
+            $result[$matches[1]] = self::parseValue($args, $arg, $opts[$matches[1]]);
+          }
+          catch(Exception $e)
+          {
+            self::$errors[] = $e->getMessage();
+            return false;
+          }
+        }
+        else
+        {
+          self::$errors[] = 'Invalid option \'' . $matches[1] . '\'';
+          return false;
+        }
+      }
+      elseif(preg_match('/^-([a-z])/', $arg, $matches)) //short options start with '-', are case-sensitive
+      {
+        foreach(str_split($matches[1]) as $o)
+        {
+          if(isset($short_opts[$o]))
+          {
+            try
+            {
+              $result[$short_opts[$o]] = self::parseValue($args, $arg, $opts[$short_opts[$o]]);
+            }
+            catch(Exception $e)
+            {
+              self::$errors[] = $e->getMessage();
+              return false;
+            }
+          }
+          else
+          {
+            self::$errors[] = 'Invalid option \'' . $matches[1] . '\'';
+            return false;
+          }
+        }
+      }
+      else
+      {
+        array_unshift($args, $arg);
+        break;
+      }
+    }
+    
+    return $result;
+  }
+  
+  static function errors()
+  {
+    return self::$errors;
+  }
+  
+  private static function normalizeTpl($opts)
+  {
+    foreach($opts as &$tpl)
+    {
+      $ntpl = array();
+      foreach($tpl as $k => $t)
+      {
+        if(is_string($k))
+          $ntpl[$k] = $t;
+        elseif(is_int($k) && is_string($t))
+          $ntpl[$t] = true;
+      }
+      $tpl = $ntpl;
+    }
+    
+    return $opts;
+  }
+  
+  /**
+   * Get the associations between short and long options, if any exist
+   *
+   * @param array $opts Options to parse
+   * @return array List of mappings between short_options => long_options
+   */
+  private static function mapShortOpts($opts)
+  {
+    $result = array();
+    
+    foreach($opts as $k => $o)
+    {
+      if(!empty($o['short']))
+        $result[$o['short']] = $k;
+    }
+    
+    return $result;
+  }
+  
+  /**
+   * Get "value" part of the long, if any, from the arguments list.
+   *
+   * Note: $args might be modified depending on the given option template
+   *
+   * @param array $args List of command-line arguments
+   * @param string $arg Argument being parsed
+   * @param array $tpl Template for the argument being parsed
+   * @return mixed Parsed option value, null if no value required
+   */
+  private static function parseValue(&$args, $arg, $tpl)
+  {
+    foreach($tpl as $t => $v)
+    {
+      switch($t)
+      {
+        case 'req_val':
+          if(strpos($arg, '=') === false)
+          {
+            if(!empty($args))
+              return array_shift($args);
+            else
+              throw new Exception('Missing option value');
+          }
+          else
+            return substr(strstr($arg, '='), 1);
+          break;
+        case 'opt_val':
+          if(strpos($arg, '=') !== false)
+            return substr(strstr($arg, '='), 1);
+          break;
+      }
+    }
+    
+    return null;
+  }
+}
+
+?>

File lib/Helper.class.php

 
 class Helper
 {
+  static protected $config_tpl = array(
+    'config' => array('short' => 'c', 'req_val'),
+    'host' => array('req_val'),
+    'user' => array('req_val'),
+    'password' => array('req_val'),
+    'db' => array('req_val'),
+    'savedir' => array('req_val'),
+    'verbose' => array('req_val'),
+    'versiontable' => array('req_val')
+  );
+  
   static protected $config = array(
     'config' => null, //path to alternate config file
     'host' => null,
     $parsed_args = array('options' => array(), 'command' => array('name' => null, 'args' => array()));
 
     array_shift($args);
-    while($arg = each($args))
+    $opts = GetOpt::extractLeft($args, self::$config_tpl);
+    if($opts === false)
     {
-      list($k, $a) = $arg;
-      if(preg_match('/^--([a-z\-]+)(?:=(.+))?$/i', $a, $matches))
-      {
-        if(in_array($matches[1], array_keys(self::$config)))
-          $parsed_args['options'][$matches[1]] = isset($matches[2]) ? $matches[2] : true;
-      }
-      else
-      {
-        break;
-      }
+      Output::error('mmp: ' . reset(GetOpt::errors()));
+      exit(1);
     }
+    else
+      $parsed_args['options'] = $opts;
 
     //if we didn't traverse the full array just now, move on to command parsing
-    if($arg !== false)
-      $parsed_args['command']['name'] = $arg['value'];
+    if(!empty($args))
+      $parsed_args['command']['name'] = array_shift($args);
 
     //consider any remaining arguments as command arguments
-    while($arg = each($args))
-      $parsed_args['command']['args'][] = $arg['value'];
+    $parsed_args['command']['args'] = $args;
 
     return $parsed_args;
   }