Skip to main content

A PHP class for parsing CLI options, text and INI files.

<?php

/**
 * Command Line option exception
 *
 * Extend Exception class for custom exception type.
 */
class CommandLineOptionException extends Exception {}

/**
 * Command Line class.
 *
 * A PHP class for parsing command-line options, text and INI configuration
 * files.
 */
class CommandLine {

  /**
   * Plain-text file type const.
   *
   * @var const CONFIG_TYPE_PLAIN
   */
  const CONFIG_TYPE_PLAIN = 1;

  /**
   * Ini file type const.
   *
   * @var const CONFIG_TYPE_INI
   */
  const CONFIG_TYPE_INI = 2;

  /**
   *
   * Parses command-line arguments and returns them as an array.
   *
   * @access public
   * @static
   * @param array $args An array of command line arguments.
   * @param array $allowed Optional array of valid/whitelist options.
   * @throws CommandLineOptionException Throws exception if the option is unrecognized.
   * @return array The parsed command-line arguments as an array.
   *
   * @example
   *   -a is a single - character option that assumes a Boolean value (acts as a switch for turning script options on or off)
   *   -a -b -c are a series of single - character options, may also be condensed as -abc
   *   -a foo is a single - character option with an assigned argument
   *   -abc foo are single - character options condensed with an argument assigned to the last option (a and b would assume true while c would be assigned foo)
   *   --abc is an option provided as a multiple - character string
   *   --abc=foo is a multiple - character option with an assigned argument
   */
  static public function parseOptions($args, $allowed = array()) {

    $options = array();
    $count = count($args);

    // retrive arguments and populate $options array
    for($i = 1; $i < $count; $i++) {
      // retrieve arguments in form of --abc=foo
      if (preg_match('/^--([-A-Z0-9]+)=(.+)$/i', $args[$i], $matches)) {
        if (empty($allowed) || in_array($matches[1], $allowed)) {
          $options[$matches[1]] = $matches[2];
        } else {
          throw new CommandLineOptionException('Unrecognized option ' . $matches[1]);
        }
      }

      // retrieve --abc arguments
      else if (substr($args[$i], 0, 2) == '--') {
        $tmp = substr($args[$i], 2);
        if (empty($allowed) || in_array($tmp, $allowed)) {
          $options[$tmp] = true;
        } else {
          throw new CommandLineOptionException('Unrecognized option ' . $tmp);
        }
      }

      // retrieve -abc foo, -abc, -a foo and -a arguments
      else if ($args[$i][0] == '-' && strlen($args[$i]) > 1) {
        // set all arguments to true except for last in sequence
        for($j = 1; $j < strlen($args[$i]) - 1; $j++) {
          if (empty($allowed) || in_array($args[$i][$j], $allowed)) {
            $options[$args[$i][$j]] = true;
          } else {
            throw new CommandLineOptionException('Unrecognized option ' . $args[$i][$j]);
          }
        }

        // set last argument in compressed sequence
        $tmp = substr($args[$i], -1, 1);
        if (empty($allowed) || in_array($tmp, $allowed)) {
          // assign next $args value if is value
          if ($i + 1 < $count && $args[$i + 1][0] != '-') {
            $options[$tmp] = $args[$i + 1];
            $i++;
          }           // assign option as boolean
          else {
            $options[$tmp] = true;
          }
        } else {
          throw new CommandLineOptionException('Unrecognized option ' . $tmp);
        }
      }

      // invalid option format
      else {
        throw new CommandLineOptionException('Invalid option format at ' . $args[$i]);
      }
    }

    return $options;
  }

  /**
   * Parses a configuration file and returns the options as an array.
   *
   * @access public
   * @static
   * @param string $file The file to open.
   * @param const  $type Plain-text file or INI file.
   * @return Ambigous <multitype:, multitype:string>
   */
  static public function parseConfigFile($file, $type = CONFIG_TYPE_PLAIN) {

    $options = array();

    // process plain configuration file
    if ($type == CONFIG_TYPE_PLAIN) {
      $fp = fopen($file, 'r');

      while (!feof($fp)) {
        $line = trim(fgets($fp));

        // skip blank lines and comments
        if ($line && !preg_match('^#', $line)) {

          $pieces = explode('=', $line);
          $opt = trim($pieces[0]);
          $value = trim($pieces[1]);

          $options[$opt] = $value;
        }
      }
      fclose($fp);
    }

    // process ini configuration file
    else if ($type == CONFIG_TYPE_INI) {
      $options = parse_ini_file($file);
    }
    return $options;
  }

  /**
   * Displays a prompt and reads keyboard input.
   *
   * Prompt for user input, accept optional maximum input length
   * and callback function for validation.
   *
   * @access public
   * @static
   * @param string $label
   * @param int $length Maximum allowed input length.
   * @param function $callback
   * @return mixed|string
   */
  static public function prompt($label, $length = 255, $callback = null) {

    echo $label . ': ';
    $value = trim(fread(STDIN, 255));

    return ($callback) ? call_user_func($callback, $value) : $value;
  }

  /**
   * Displays a prompt with suggested response and reads keyboard input.
   *
   * Prompt for user input, accept optional default value, maximum
   * input length and callback function for validation.
   *
   * @access public
   * @static
   * @param string $label
   * @param int|string $default Optional default value. Default is null.
   * @param int $length Maximum allowed input length.
   * @param unknown_type $callback
   * @return mixed
   *
   * @example
   *
   * do {
   *   $db_host   = CommandLine::promptDefault('Database host', 'localhost');
   *   $db_schema = CommandLine::promptDefault('Database schema', 'TEST');
   *   $db_user   = CommandLine::promptDefault('Database user', 'TESTUSR');
   *   $db_pass   = CommandLine::prompt('Database password');
   *
   *   echo str_repeat('-', 70)       .                  "\n";
   *   echo 'Database host: '         . $db_host .       "\n";
   *   echo 'Database schema: '       . $db_schema .     "\n";
   *   echo 'Database user: '         . $db_user .       "\n";
   *   echo 'Database password: '     . $db_pass .       "\n";
   *   echo 'Database table prefix: ' . $db_tbl_prefix . "\n";
   *
   *   $ok = CommandLine::promptDefault('Is this correct?', 'yes', 3, 'strtolower');
   * }
   *
   * while ($ok != 'yes' && $ok != 'y');
   * echo "\n";
   *
   */
  static public function promptDefault($label, $default = null, $length = 255, $callback = null) {

    $label .= ' [' . $default . ']';
    $value = self::prompt($label, $length);

    if (!$value) {
      $value = $default;
    }

    return ($callback) ? call_user_func($callback, $value) : $value;
  }

}
?>