Skip to content

Instantly share code, notes, and snippets.

@njones
Created September 14, 2010 19:55
Show Gist options
  • Save njones/579660 to your computer and use it in GitHub Desktop.
Save njones/579660 to your computer and use it in GitHub Desktop.
<?php
/**
* return all of the commandline options as a nice list of things.
* This is modeled after the Python Optparse. (so it's easy!!) with some
* BASH getops flavor.
*
* @author Nika Jones
*
* @copyright 2010 Appcelerator Inc.
*
* Licensed under the terms of the Apache Public License
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
class OptParse
{
/**
* A constant for parsing ALL values and subsituting the default value
*/
const WITH_DEFAULTS = TRUE;
/**
* A bool of what state the parser is in. If it's a short option
* or not.
*/
private $is_short_option = FALSE;
/**
* The letter that represents the short option
*/
private $short_option_letter = "";
/**
* An array that holds all of the options
* in a key=value
*/
private $parse_map = array();
/**
* Maps short options to long options (i.e. -f == file)
*/
private $short_to_long_map = array();
/**
* Maps short options to long options (i.e. -f == file)
*/
private $help_map = array();
/**
* An internal space seperated string of the arguments presented on the commandline
*/
private $args;
/**
* An array of non-option arguments (this is mainly used for the auto-generated help)
*/
private $argument_name = array();
/**
* The name of the calling script
*/
private $script_name;
/**
*
*/
private $help_description = "";
/**
* The constructor to create an object.
*/
public function __construct($argvs = NULL, $script_name = ":_internal_:")
{
// The argv from the calling script...
global $argv;
$_argv = $argv;
/* Checks to see if the args are passed in or not.
* If not then use the global args, remember to
* to pass in the scriptname (for the auto-gnerated help).
*/
if(is_null($argvs))
{
$this->script_name = array_shift($_argv);
// Args that have spaces in them, should be quoted... so this will do that.
$this->args = $_argv;
}
else
{
$this->script_name = $script_name;
$this->args = (array)$argvs;
}
// this is the inital set up so the -h can be help (this can be overriden)
$this->short_to_long_map["-h"] = "help";
}
/**
* The static method way of calling the option parse class.
* This is the preferred way of starting things.
*
* @param string $argvs an array of arguments (i.e. -f FILENAME --with-path=/path)
would equal: array("-f", "FILENAME", "--with-path=/path")
*
* @returns object OptParse
*/
public static function OptionParser($argvs = NULL, $script_name = ":_internal_:")
{
return new optparse($argvs, $script_name);
}
/**
* How to add an option to the parser. So that it can be recognized
* and placed automatically into the help -h option (unless overridden)
*
* @param string $argument_name The long name of the option (i.e. --file)
* @param string $description A description which will be in the help section
* @param int $index A specific index, so we can have optional arguments for
* the same index position
* @returns null
*/
public function add_description($description)
{
$this->help_description = $description;
}
/**
* How to add an option to the parser. So that it can be recognized
* and placed automatically into the help -h option (unless overridden)
*
* @param string $argument_name The long name of the option (i.e. --file)
* @param string $description A description which will be in the help section
* @param int $index A specific index, so we can have optional arguments for
* the same index position
* @returns null
*/
public function add_argument($argument_name, $description = NULL, $index = NULL)
{
static $n = 1;
if(!isset($index))
{
$index = $n++;
}
else
{
$n = $index >= $n ? $index + 1 : $n; // add one to the
}
$this->argument_name[] = array($argument_name, $index);
$this->help_map[$argument_name]["description"] = $description;
}
/**
* Adds an option to the parser
*
* @params string $long The long option (i.e. --file)
* @params string $short The short option to use (i.e. -f)
* @params array $options The actual options.
*/
public function add_option($long, $short, $options = array())
{
static $internal_counter = 1;
if($long == "--help") // then remove the default mapping
{
unset($this->short_to_long_map["-h"]);
}
// Check to make sure that there is atleast an option!!
if(empty($long) && empty($short))
{
throw new Exception("You must have atleast one option.");
}
// Account for a missiong long option
if(empty($long))
{
$long = "-" . $short . "__" . $internal_counter++ . "__";
}
// Account for a missing short option
if(empty($short))
{
$short = "-" . $long[2] . "__" . $internal_counter++ . "__";
}
// remove the --'s
$long = substr($long, 2);
// Note short will have the "-" prefix
$this->short_to_long_map[$short] = $long;
$this->long_to_short_map[$long] = $short;
if(!isset($this->parse_map[$long]))
{
$this->parse_map[$long] = array();
}
if(isset($options["metavar"]))
{
$this->parse_map[$long]["metavar"] = $options["metavar"];
}
if(isset($options["default"]))
{
$this->parse_map[$long]["default"] = $options["default"];
}
if(isset($options["description"]))
{
$this->help_map[$long]["description"] = $options["description"];
}
}
/**
* Return the name of the running script. (good for nested parsings)
*/
public function name()
{
return $this->script_name;
}
/**
* Parse the arguments out and place into the appropiate place in an array
*
* @params const $with_defaults Will fill in all default values, otherwise a non
* used value will not be set or NULL
*/
public function parse_args($with_defaults = NULL)
{
$arguments = array();
$defaults = $options = array();
if($with_defaults == self::WITH_DEFAULTS)
{
foreach($this->parse_map as $long_default_key => $long_default)
{
if(isset($this->parse_map[$long_default_key]["default"]))
{
$defaults[$long_default_key] = $this->parse_map[$long_default_key]["default"];
}
}
}
$length = count($this->args) - 1;
while(list($index, $args) = each($this->args))
{
if(preg_match("/^--(.*)/", $args, $match))
{
list($long, $value) = array_pad(explode("=", $match[1]), 2, NULL);
$options[$long] = $value;
}
elseif(preg_match("/^(-(.))(.*)/", $args, $match))
{
if(!empty($match[3]))
{
// Make into individual flags
foreach(str_split($match[2] . $match[3]) as $flag)
{
if(isset($this->short_to_long_map["-" . $flag]))
{
$long = $this->short_to_long_map["-" . $flag];
$value = isset($this->parse_map[$long]["default"]) ? $this->parse_map[$long]["default"] : TRUE;
$options[$long] = $value;
}
else
{
if("-$flag" == $match[1])
{
throw new Exception("The short key -" . $flag . " is not an option.", 500);
}
$long = $this->short_to_long_map[$match[1]];
$value = $match[3];
$options[$long] = $value;
break;
}
}
}
else
{
if(!isset($this->short_to_long_map[$match[1]]))
{
throw new Exception("The short key " . $match[1] . " is not an option.", 500);
}
$long = $this->short_to_long_map[$match[1]];
$current = current($this->args);
$value = $index == $length && $current == FALSE ? TRUE : $current;
$options[$long] = $value;
next($this->args); // skip the next one.
}
}
else
{
$arguments[] = $args;
}
}
if(isset($options["help"])) { $options["help"] = $this->build_help(); }
return array(array_merge($defaults, $options), $arguments);
}
/**
* Automatically build the help screen, out of the values we have for the options and
* arugments added to the parser.
*/
private function build_help()
{
// build the help text if theres a need
$argument_names = "";
foreach((array)$this->argument_name as $arg)
{
$argument_list[$arg[1]][] = $arg[0];
}
foreach($argument_list as $argument_group)
{
$argument_grouped = implode("|", $argument_group);
$argument_line[] = count($argument_group) > 1 ? "[" . $argument_grouped . "]" : $argument_grouped;
}
$arguments = trim(implode(" ", $argument_line));
$_padding = 25;
$scriptname = ltrim($this->script_name, './');
// $help_text = "HELP FOR " . $scriptname . "\n\n";
$help_text = "usage " . $scriptname . " " . $arguments . " [OPTIONS]\n\n";
$help_text .= (empty($this->help_description) ? "" : "Description:\n" . $this->help_description . "\n\n");
$help_text .= "Options:\n";
$help_map = $this->help_map;
$help_map["--help"] = array("description"=>"This help screen.");
if(!isset($this->long_to_short_map["--help"])) { $this->long_to_short_map["--help"] = "-h"; }
ksort($help_map);
foreach($help_map as $help_long => $help_data)
{
if($help_long[0] == "-")
{
// Check the option to see if it's internal or not, so we know to display it or not.
$opt = array();
$opt[] = preg_match("/__\d+__$/", $this->long_to_short_map[$help_long]) ? NULL : $this->long_to_short_map[$help_long];
$opt[] = preg_match("/__\d+__$/", $help_long) ? NULL : $help_long;
$help_opts = implode(", ", array_filter($opt));
$help_opts .= isset($this->parse_map[$help_long]["metavar"]) ? (isset($opt[1]) ? "=" : " ") . $this->parse_map[$help_long]["metavar"] : "";
$help_line = str_pad($help_opts, $_padding);
$help_line .= " " . $help_data["description"]."\n";
// If there's a defaut then display that on the next line.
if(isset($this->parse_map[$help_long]["default"]))
{
$help_line .= str_pad("", $_padding) . " default : " . $this->parse_map[$help_long]["default"] . "\n\n";
}
$help_text .= $help_line;
}
else
{
if(!isset($has_space)) { $help_text .= "\nArguments:\n"; $has_space = TRUE; }
$help_text .= str_pad($help_long, $_padding) . $help_data["description"] . "\n";
}
}
return $help_text;
}
/**
* Make sure that all options are properly quoted
*
* @param string $arg a single argument of a commandline command.
*/
private function quote_it($arg)
{
return str_replace('""', "", preg_replace("/^(--?\w+=?|\w+=)?(.*)/", "$1\"$2\"", $arg));
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment