Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2680357
PhutilArgumentParser.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
28 KB
Referenced Files
None
Subscribers
None
PhutilArgumentParser.php
View Options
<?php
/**
* Parser for command-line arguments for scripts. Like similar parsers, this
* class allows you to specify, validate, and render help for command-line
* arguments. For example:
*
* name=create_dog.php
* $args = new PhutilArgumentParser($argv);
* $args->setTagline('make an new dog')
* $args->setSynopsis(<<<EOHELP
* **dog** [--big] [--name __name__]
* Create a new dog. How does it work? Who knows.
* EOHELP
* );
* $args->parse(
* array(
* array(
* 'name' => 'name',
* 'param' => 'dogname',
* 'default' => 'Rover',
* 'help' => 'Set the dog\'s name. By default, the dog will be '.
* 'named "Rover".',
* ),
* array(
* 'name' => 'big',
* 'short' => 'b',
* 'help' => 'If set, create a large dog.',
* ),
* ));
*
* $dog_name = $args->getArg('name');
* $dog_size = $args->getArg('big') ? 'big' : 'small';
*
* // ... etc ...
*
* (For detailed documentation on supported keys in argument specifications,
* see @{class:PhutilArgumentSpecification}.)
*
* This will handle argument parsing, and generate appropriate usage help if
* the user provides an unsupported flag. @{class:PhutilArgumentParser} also
* supports some builtin "standard" arguments:
*
* $args->parseStandardArguments();
*
* See @{method:parseStandardArguments} for details. Notably, this includes
* a "--help" flag, and an "--xprofile" flag for profiling command-line scripts.
*
* Normally, when the parser encounters an unknown flag, it will exit with
* an error. However, you can use @{method:parsePartial} to consume only a
* set of flags:
*
* $args->parsePartial($spec_list);
*
* This allows you to parse some flags before making decisions about other
* parsing, or share some flags across scripts. The builtin standard arguments
* are implemented in this way.
*
* There is also builtin support for "workflows", which allow you to build a
* script that operates in several modes (e.g., by accepting commands like
* `install`, `upgrade`, etc), like `arc` does. For detailed documentation on
* workflows, see @{class:PhutilArgumentWorkflow}.
*
* @task parse Parsing Arguments
* @task read Reading Arguments
* @task help Command Help
* @task internal Internals
*/
final
class
PhutilArgumentParser
extends
Phobject
{
private
$bin
;
private
$argv
;
private
$specs
=
array
(
)
;
private
$results
=
array
(
)
;
private
$parsed
;
private
$tagline
;
private
$synopsis
;
private
$workflows
;
private
$helpWorkflows
;
private
$showHelp
;
private
$requireArgumentTerminator
=
false
;
private
$sawTerminator
=
false
;
const
PARSE_ERROR_CODE
=
77
;
private
static
$traceModeEnabled
=
false
;
/* -( Parsing Arguments )-------------------------------------------------- */
/**
* Build a new parser. Generally, you start a script with:
*
* $args = new PhutilArgumentParser($argv);
*
* @param list $argv Argument vector to parse, generally the $argv global.
* @task parse
*/
public
function
__construct
(
array
$argv
)
{
$this
->
bin
=
$argv
[
0
]
;
$this
->
argv
=
array_slice
(
$argv
,
1
)
;
}
/**
* Parse and consume a list of arguments, removing them from the argument
* vector but leaving unparsed arguments for later consumption. You can
* retrieve unconsumed arguments directly with
* @{method:getUnconsumedArgumentVector}. Doing a partial parse can make it
* easier to share common flags across scripts or workflows.
*
* @param list $specs List of argument specs, see
* @{class:PhutilArgumentSpecification}.
* @param bool $initial_only (optional) Require flags appear before any
* non-flag arguments.
* @return $this
* @task parse
*/
public
function
parsePartial
(
array
$specs
,
$initial_only
=
false
)
{
return
$this
->
parseInternal
(
$specs
,
false
,
$initial_only
)
;
}
/**
* @return $this
*/
private
function
parseInternal
(
array
$specs
,
$correct_spelling
,
$initial_only
)
{
$specs
=
PhutilArgumentSpecification
::
newSpecsFromList
(
$specs
)
;
$this
->
mergeSpecs
(
$specs
)
;
// Wildcard arguments have a name like "argv", but we don't want to
// parse a corresponding flag like "--argv". Filter them out before
// building a list of available flags.
$non_wildcard
=
array
(
)
;
foreach
(
$specs
as
$spec_key
=>
$spec
)
{
if
(
$spec
->
getWildcard
(
)
)
{
continue
;
}
$non_wildcard
[
$spec_key
]
=
$spec
;
}
$specs_by_name
=
mpull
(
$non_wildcard
,
null
,
'getName'
)
;
$specs_by_short
=
mpull
(
$non_wildcard
,
null
,
'getShortAlias'
)
;
unset
(
$specs_by_short
[
null
]
)
;
$argv
=
$this
->
argv
;
$len
=
count
(
$argv
)
;
$is_initial
=
true
;
for
(
$ii
=
0
;
$ii
<
$len
;
$ii
++
)
{
$arg
=
$argv
[
$ii
]
;
$map
=
null
;
$options
=
null
;
if
(
!
is_string
(
$arg
)
)
{
// Non-string argument; pass it through as-is.
}
else
if
(
$arg
==
'--'
)
{
// This indicates "end of flags".
$this
->
sawTerminator
=
true
;
break
;
}
else
if
(
$arg
==
'-'
)
{
// This is a normal argument (e.g., stdin).
continue
;
}
else
if
(
!
strncmp
(
'--'
,
$arg
,
2
)
)
{
$pre
=
'--'
;
$arg
=
substr
(
$arg
,
2
)
;
$map
=
$specs_by_name
;
$options
=
array_keys
(
$specs_by_name
)
;
}
else
if
(
!
strncmp
(
'-'
,
$arg
,
1
)
&&
strlen
(
$arg
)
>
1
)
{
$pre
=
'-'
;
$arg
=
substr
(
$arg
,
1
)
;
$map
=
$specs_by_short
;
}
else
{
$is_initial
=
false
;
}
if
(
$map
)
{
$val
=
null
;
$parts
=
explode
(
'='
,
$arg
,
2
)
;
if
(
count
(
$parts
)
==
2
)
{
list
(
$arg
,
$val
)
=
$parts
;
}
// Try to correct flag spelling for full flags, to allow users to make
// minor mistakes.
if
(
$correct_spelling
&&
$options
&&
!
isset
(
$map
[
$arg
]
)
)
{
$corrections
=
PhutilArgumentSpellingCorrector
::
newFlagCorrector
(
)
->
correctSpelling
(
$arg
,
$options
)
;
$should_autocorrect
=
$this
->
shouldAutocorrect
(
)
;
if
(
count
(
$corrections
)
==
1
&&
$should_autocorrect
)
{
$corrected
=
head
(
$corrections
)
;
$this
->
logMessage
(
tsprintf
(
"%s\n"
,
pht
(
'(Assuming "%s" is the British spelling of "%s".)'
,
$pre
.
$arg
,
$pre
.
$corrected
)
)
)
;
$arg
=
$corrected
;
}
}
if
(
isset
(
$map
[
$arg
]
)
)
{
if
(
$initial_only
&&
!
$is_initial
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Argument "%s" appears after the first non-flag argument. '
.
'This special argument must appear before other arguments.'
,
"{$pre}{$arg}"
)
)
;
}
$spec
=
$map
[
$arg
]
;
unset
(
$argv
[
$ii
]
)
;
$param_name
=
$spec
->
getParamName
(
)
;
if
(
$val
!==
null
)
{
if
(
$param_name
===
null
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Argument "%s" does not take a parameter.'
,
"{$pre}{$arg}"
)
)
;
}
}
else
{
if
(
$param_name
!==
null
)
{
if
(
$ii
+
1
<
$len
)
{
$val
=
$argv
[
$ii
+
1
]
;
unset
(
$argv
[
$ii
+
1
]
)
;
$ii
++
;
}
else
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Argument "%s" requires a parameter.'
,
"{$pre}{$arg}"
)
)
;
}
}
else
{
$val
=
true
;
}
}
if
(
!
$spec
->
getRepeatable
(
)
)
{
if
(
array_key_exists
(
$spec
->
getName
(
)
,
$this
->
results
)
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Argument "%s" was provided twice.'
,
"{$pre}{$arg}"
)
)
;
}
}
$conflicts
=
$spec
->
getConflicts
(
)
;
foreach
(
$conflicts
as
$conflict
=>
$reason
)
{
if
(
array_key_exists
(
$conflict
,
$this
->
results
)
)
{
if
(
!
is_string
(
$reason
)
||
!
strlen
(
$reason
)
)
{
$reason
=
'.'
;
}
else
{
$reason
=
': '
.
$reason
.
'.'
;
}
throw
new
PhutilArgumentUsageException
(
pht
(
'Argument "%s" conflicts with argument "%s"%s'
,
"{$pre}{$arg}"
,
"--{$conflict}"
,
$reason
)
)
;
}
}
if
(
$spec
->
getRepeatable
(
)
)
{
if
(
$spec
->
getParamName
(
)
===
null
)
{
if
(
empty
(
$this
->
results
[
$spec
->
getName
(
)
]
)
)
{
$this
->
results
[
$spec
->
getName
(
)
]
=
0
;
}
$this
->
results
[
$spec
->
getName
(
)
]
++
;
}
else
{
$this
->
results
[
$spec
->
getName
(
)
]
[
]
=
$val
;
}
}
else
{
$this
->
results
[
$spec
->
getName
(
)
]
=
$val
;
}
}
}
}
foreach
(
$specs
as
$spec
)
{
if
(
$spec
->
getWildcard
(
)
)
{
$this
->
results
[
$spec
->
getName
(
)
]
=
$this
->
filterWildcardArgv
(
$argv
)
;
$argv
=
array
(
)
;
break
;
}
}
$this
->
argv
=
array_values
(
$argv
)
;
return
$this
;
}
/**
* Parse and consume a list of arguments, throwing an exception if there is
* anything left unconsumed. This is like @{method:parsePartial}, but raises
* a {class:PhutilArgumentUsageException} if there are leftovers.
*
* Normally, you would call @{method:parse} instead, which emits a
* user-friendly error. You can also use @{method:printUsageException} to
* render the exception in a user-friendly way.
*
* @param list $specs List of argument specs, see
* @{class:PhutilArgumentSpecification}.
* @return $this
* @task parse
*/
public
function
parseFull
(
array
$specs
)
{
$this
->
parseInternal
(
$specs
,
true
,
false
)
;
// If we have remaining unconsumed arguments other than a single "--",
// fail.
$argv
=
$this
->
filterWildcardArgv
(
$this
->
argv
)
;
if
(
$argv
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Unrecognized argument "%s".'
,
head
(
$argv
)
)
)
;
}
if
(
$this
->
getRequireArgumentTerminator
(
)
)
{
if
(
!
$this
->
sawTerminator
)
{
throw
new
ArcanistMissingArgumentTerminatorException
(
)
;
}
}
if
(
$this
->
showHelp
)
{
$this
->
printHelpAndExit
(
)
;
}
return
$this
;
}
/**
* Parse and consume a list of arguments, raising a user-friendly error if
* anything remains. See also @{method:parseFull} and @{method:parsePartial}.
*
* @param list $specs List of argument specs, see
* @{class:PhutilArgumentSpecification}.
* @return $this
* @task parse
*/
public
function
parse
(
array
$specs
)
{
try
{
return
$this
->
parseFull
(
$specs
)
;
}
catch
(
PhutilArgumentUsageException
$ex
)
{
$this
->
printUsageException
(
$ex
)
;
exit
(
self
::
PARSE_ERROR_CODE
)
;
}
}
/**
* Parse and execute workflows, raising a user-friendly error if anything
* remains. See also @{method:parseWorkflowsFull}.
*
* See @{class:PhutilArgumentWorkflow} for details on using workflows.
*
* @param list $workflows List of argument specs, see
* @{class:PhutilArgumentSpecification}.
* @return $this
* @task parse
*/
public
function
parseWorkflows
(
array
$workflows
)
{
try
{
return
$this
->
parseWorkflowsFull
(
$workflows
)
;
}
catch
(
PhutilArgumentUsageException
$ex
)
{
$this
->
printUsageException
(
$ex
)
;
exit
(
self
::
PARSE_ERROR_CODE
)
;
}
}
/**
* Select a workflow. For commands that may operate in several modes, like
* `arc`, the modes can be split into "workflows". Each workflow specifies
* the arguments it accepts. This method takes a list of workflows, selects
* the chosen workflow, parses its arguments, and either executes it (if it
* is executable) or returns it for handling.
*
* See @{class:PhutilArgumentWorkflow} for details on using workflows.
*
* @param list $workflows List of @{class:PhutilArgumentWorkflow}s.
* @return PhutilArgumentWorkflow|no Returns the chosen workflow if it is
* not executable, or executes it and
* exits with a return code if it is.
* @task parse
*/
public
function
parseWorkflowsFull
(
array
$workflows
)
{
assert_instances_of
(
$workflows
,
'PhutilArgumentWorkflow'
)
;
// Clear out existing workflows. We need to do this to permit the
// construction of sub-workflows.
$this
->
workflows
=
array
(
)
;
foreach
(
$workflows
as
$workflow
)
{
$name
=
$workflow
->
getName
(
)
;
if
(
$name
===
null
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'Workflow has no name!'
)
)
;
}
if
(
isset
(
$this
->
workflows
[
$name
]
)
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
"Two workflows with name '%s!"
,
$name
)
)
;
}
$this
->
workflows
[
$name
]
=
$workflow
;
}
$argv
=
$this
->
argv
;
if
(
empty
(
$argv
)
)
{
// TODO: this is kind of hacky / magical.
if
(
isset
(
$this
->
workflows
[
'help'
]
)
)
{
$argv
=
array
(
'help'
)
;
}
else
{
throw
new
PhutilArgumentUsageException
(
pht
(
'No workflow selected.'
)
)
;
}
}
$flow
=
array_shift
(
$argv
)
;
if
(
empty
(
$this
->
workflows
[
$flow
]
)
)
{
$corrected
=
PhutilArgumentSpellingCorrector
::
newCommandCorrector
(
)
->
correctSpelling
(
$flow
,
array_keys
(
$this
->
workflows
)
)
;
$should_autocorrect
=
$this
->
shouldAutocorrect
(
)
;
if
(
count
(
$corrected
)
==
1
&&
$should_autocorrect
)
{
$corrected
=
head
(
$corrected
)
;
$this
->
logMessage
(
tsprintf
(
"%s\n"
,
pht
(
'(Assuming "%s" is the British spelling of "%s".)'
,
$flow
,
$corrected
)
)
)
;
$flow
=
$corrected
;
}
else
{
if
(
!
$this
->
showHelp
)
{
$this
->
raiseUnknownWorkflow
(
$flow
,
$corrected
)
;
}
}
}
$workflow
=
idx
(
$this
->
workflows
,
$flow
)
;
if
(
$this
->
showHelp
)
{
// Make "cmd flow --help" behave like "cmd help flow", not "cmd help".
$help_flow
=
idx
(
$this
->
workflows
,
'help'
)
;
if
(
$help_flow
)
{
if
(
$help_flow
!==
$workflow
)
{
$workflow
=
$help_flow
;
$argv
=
array
(
$flow
)
;
// Prevent parse() from dumping us back out to standard help.
$this
->
showHelp
=
false
;
}
}
else
{
$this
->
printHelpAndExit
(
)
;
}
}
if
(
!
$workflow
)
{
$this
->
raiseUnknownWorkflow
(
$flow
,
$corrected
)
;
}
$this
->
argv
=
array_values
(
$argv
)
;
if
(
$workflow
->
shouldParsePartial
(
)
)
{
$this
->
parsePartial
(
$workflow
->
getArguments
(
)
)
;
}
else
{
$this
->
parse
(
$workflow
->
getArguments
(
)
)
;
}
if
(
$workflow
->
isExecutable
(
)
)
{
$workflow
->
setArgv
(
$this
)
;
$err
=
$workflow
->
execute
(
$this
)
;
exit
(
$err
)
;
}
else
{
return
$workflow
;
}
}
/**
* Parse "standard" arguments and apply their effects:
*
* --trace Enable service call tracing.
* --no-ansi Disable ANSI color/style sequences.
* --xprofile <file> Write out an XHProf profile.
* --help Show help.
*
* @return $this
*
* @phutil-external-symbol function xhprof_enable
*/
public
function
parseStandardArguments
(
)
{
try
{
$this
->
parsePartial
(
array
(
array
(
'name'
=>
'trace'
,
'help'
=>
pht
(
'Trace command execution and show service calls.'
)
,
'standard'
=>
true
,
)
,
array
(
'name'
=>
'no-ansi'
,
'help'
=>
pht
(
'Disable ANSI terminal codes, printing plain text with '
.
'no color or style.'
)
,
'conflicts'
=>
array
(
'ansi'
=>
null
,
)
,
'standard'
=>
true
,
)
,
array
(
'name'
=>
'ansi'
,
'help'
=>
pht
(
"Use formatting even in environments which probably "
.
"don't support it."
)
,
'standard'
=>
true
,
)
,
array
(
'name'
=>
'xprofile'
,
'param'
=>
'profile'
,
'help'
=>
pht
(
'Profile script execution and write results to a file.'
)
,
'standard'
=>
true
,
)
,
array
(
'name'
=>
'help'
,
'short'
=>
'h'
,
'help'
=>
pht
(
'Show this help.'
)
,
'standard'
=>
true
,
)
,
array
(
'name'
=>
'show-standard-options'
,
'help'
=>
pht
(
'Show every option, including standard options like this one.'
)
,
'standard'
=>
true
,
)
,
array
(
'name'
=>
'recon'
,
'help'
=>
pht
(
'Start in remote console mode.'
)
,
'standard'
=>
true
,
)
,
)
)
;
}
catch
(
PhutilArgumentUsageException
$ex
)
{
$this
->
printUsageException
(
$ex
)
;
exit
(
self
::
PARSE_ERROR_CODE
)
;
}
if
(
$this
->
getArg
(
'trace'
)
)
{
PhutilServiceProfiler
::
installEchoListener
(
)
;
self
::
$traceModeEnabled
=
true
;
}
if
(
$this
->
getArg
(
'no-ansi'
)
)
{
PhutilConsoleFormatter
::
disableANSI
(
true
)
;
}
if
(
$this
->
getArg
(
'ansi'
)
)
{
PhutilConsoleFormatter
::
disableANSI
(
false
)
;
}
if
(
$this
->
getArg
(
'help'
)
)
{
$this
->
showHelp
=
true
;
}
$xprofile
=
$this
->
getArg
(
'xprofile'
)
;
if
(
$xprofile
)
{
if
(
!
function_exists
(
'xhprof_enable'
)
)
{
throw
new
Exception
(
pht
(
'To use "--xprofile", you must install XHProf.'
)
)
;
}
xhprof_enable
(
0
)
;
register_shutdown_function
(
array
(
$this
,
'shutdownProfiler'
)
)
;
}
$recon
=
$this
->
getArg
(
'recon'
)
;
if
(
$recon
)
{
$remote_console
=
PhutilConsole
::
newRemoteConsole
(
)
;
$remote_console
->
beginRedirectOut
(
)
;
PhutilConsole
::
setConsole
(
$remote_console
)
;
}
else
if
(
$this
->
getArg
(
'trace'
)
)
{
$server
=
new
PhutilConsoleServer
(
)
;
$server
->
setEnableLog
(
true
)
;
$console
=
PhutilConsole
::
newConsoleForServer
(
$server
)
;
PhutilConsole
::
setConsole
(
$console
)
;
}
return
$this
;
}
/* -( Reading Arguments )-------------------------------------------------- */
public
function
getArg
(
$name
)
{
if
(
empty
(
$this
->
specs
[
$name
]
)
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'No specification exists for argument "%s"!'
,
$name
)
)
;
}
if
(
idx
(
$this
->
results
,
$name
)
!==
null
)
{
return
$this
->
results
[
$name
]
;
}
return
$this
->
specs
[
$name
]
->
getDefault
(
)
;
}
public
function
getArgAsInteger
(
$name
)
{
$value
=
$this
->
getArg
(
$name
)
;
if
(
$value
===
null
)
{
return
$value
;
}
if
(
!
preg_match
(
'/^-?\d+\z/'
,
$value
)
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Parameter provided to argument "--%s" must be an integer.'
,
$name
)
)
;
}
$intvalue
=
(int)
$value
;
if
(
phutil_string_cast
(
$intvalue
)
!==
phutil_string_cast
(
$value
)
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Parameter provided to argument "--%s" is too large to '
.
'parse as an integer.'
,
$name
)
)
;
}
return
$intvalue
;
}
public
function
getUnconsumedArgumentVector
(
)
{
return
$this
->
argv
;
}
public
function
setUnconsumedArgumentVector
(
array
$argv
)
{
$this
->
argv
=
$argv
;
return
$this
;
}
public
function
setWorkflows
(
$workflows
)
{
$workflows
=
mpull
(
$workflows
,
null
,
'getName'
)
;
$this
->
workflows
=
$workflows
;
return
$this
;
}
public
function
setHelpWorkflows
(
array
$help_workflows
)
{
$help_workflows
=
mpull
(
$help_workflows
,
null
,
'getName'
)
;
$this
->
helpWorkflows
=
$help_workflows
;
return
$this
;
}
public
function
getWorkflows
(
)
{
return
$this
->
workflows
;
}
/* -( Command Help )------------------------------------------------------- */
public
function
setRequireArgumentTerminator
(
$require
)
{
$this
->
requireArgumentTerminator
=
$require
;
return
$this
;
}
public
function
getRequireArgumentTerminator
(
)
{
return
$this
->
requireArgumentTerminator
;
}
public
function
setSynopsis
(
$synopsis
)
{
$this
->
synopsis
=
$synopsis
;
return
$this
;
}
public
function
setTagline
(
$tagline
)
{
$this
->
tagline
=
$tagline
;
return
$this
;
}
public
function
printHelpAndExit
(
)
{
echo
$this
->
renderHelp
(
)
;
exit
(
self
::
PARSE_ERROR_CODE
)
;
}
public
function
renderHelp
(
)
{
$out
=
array
(
)
;
$more
=
array
(
)
;
if
(
$this
->
bin
)
{
$out
[
]
=
$this
->
format
(
'**%s**'
,
pht
(
'NAME'
)
)
;
$name
=
$this
->
indent
(
6
,
'**%s**'
,
basename
(
$this
->
bin
)
)
;
if
(
$this
->
tagline
)
{
$name
.=
$this
->
format
(
' - '
.
$this
->
tagline
)
;
}
$out
[
]
=
$name
;
$out
[
]
=
null
;
}
if
(
$this
->
synopsis
)
{
$out
[
]
=
$this
->
format
(
'**%s**'
,
pht
(
'SYNOPSIS'
)
)
;
$out
[
]
=
$this
->
indent
(
6
,
$this
->
synopsis
)
;
$out
[
]
=
null
;
}
$workflows
=
$this
->
helpWorkflows
;
if
(
$workflows
===
null
)
{
$workflows
=
$this
->
workflows
;
}
if
(
$workflows
)
{
$has_help
=
false
;
$out
[
]
=
$this
->
format
(
'**%s**'
,
pht
(
'WORKFLOWS'
)
)
;
$out
[
]
=
null
;
$flows
=
$workflows
;
ksort
(
$flows
)
;
foreach
(
$flows
as
$workflow
)
{
if
(
$workflow
->
getName
(
)
==
'help'
)
{
$has_help
=
true
;
}
$out
[
]
=
$this
->
renderWorkflowHelp
(
$workflow
->
getName
(
)
,
$show_details
=
false
)
;
}
if
(
$has_help
)
{
$more
[
]
=
pht
(
'Use **%s** __command__ for a detailed command reference.'
,
'help'
)
;
}
}
$specs
=
$this
->
renderArgumentSpecs
(
$this
->
specs
)
;
if
(
$specs
)
{
$out
[
]
=
$this
->
format
(
'**%s**'
,
pht
(
'OPTION REFERENCE'
)
)
;
$out
[
]
=
null
;
$out
[
]
=
$specs
;
}
// If we have standard options but no --show-standard-options, print out
// a quick hint about it.
if
(
!
empty
(
$this
->
specs
[
'show-standard-options'
]
)
&&
!
$this
->
getArg
(
'show-standard-options'
)
)
{
$more
[
]
=
pht
(
'Use __%s__ to show additional options.'
,
'--show-standard-options'
)
;
}
$out
[
]
=
null
;
if
(
$more
)
{
foreach
(
$more
as
$hint
)
{
$out
[
]
=
$this
->
indent
(
0
,
$hint
)
;
}
$out
[
]
=
null
;
}
return
implode
(
"\n"
,
$out
)
;
}
public
function
renderWorkflowHelp
(
$workflow_name
,
$show_details
=
false
)
{
$out
=
array
(
)
;
$indent
=
(
$show_details
?
0
:
6
)
;
$workflows
=
$this
->
helpWorkflows
;
if
(
$workflows
===
null
)
{
$workflows
=
$this
->
workflows
;
}
$workflow
=
idx
(
$workflows
,
strtolower
(
$workflow_name
)
)
;
if
(
!
$workflow
)
{
$out
[
]
=
$this
->
indent
(
$indent
,
pht
(
'There is no **%s** workflow.'
,
$workflow_name
)
)
;
}
else
{
$out
[
]
=
$this
->
indent
(
$indent
,
$workflow
->
getExamples
(
)
)
;
$synopsis
=
$workflow
->
getSynopsis
(
)
;
if
(
$synopsis
!==
null
)
{
$out
[
]
=
$this
->
indent
(
$indent
,
$workflow
->
getSynopsis
(
)
)
;
}
if
(
$show_details
)
{
$full_help
=
$workflow
->
getHelp
(
)
;
if
(
$full_help
)
{
$out
[
]
=
null
;
$out
[
]
=
$this
->
indent
(
$indent
,
$full_help
)
;
}
$specs
=
$this
->
renderArgumentSpecs
(
$workflow
->
getArguments
(
)
)
;
if
(
$specs
)
{
$out
[
]
=
null
;
$out
[
]
=
$specs
;
}
}
}
$out
[
]
=
null
;
return
implode
(
"\n"
,
$out
)
;
}
public
function
printUsageException
(
PhutilArgumentUsageException
$ex
)
{
$message
=
tsprintf
(
"**%s** %B\n"
,
pht
(
'Usage Exception:'
)
,
$ex
->
getMessage
(
)
)
;
$this
->
logMessage
(
$message
)
;
}
private
function
logMessage
(
$message
)
{
PhutilSystem
::
writeStderr
(
$message
)
;
}
/* -( Internals )---------------------------------------------------------- */
private
function
filterWildcardArgv
(
array
$argv
)
{
foreach
(
$argv
as
$key
=>
$value
)
{
if
(
$value
==
'--'
)
{
unset
(
$argv
[
$key
]
)
;
break
;
}
else
if
(
is_string
(
$value
)
&&
!
strncmp
(
$value
,
'-'
,
1
)
&&
strlen
(
$value
)
>
1
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Argument "%s" is unrecognized. Use "%s" to indicate '
.
'the end of flags.'
,
$value
,
'--'
)
)
;
}
}
return
array_values
(
$argv
)
;
}
private
function
mergeSpecs
(
array
$specs
)
{
$short_map
=
mpull
(
$this
->
specs
,
null
,
'getShortAlias'
)
;
unset
(
$short_map
[
null
]
)
;
$wildcard
=
null
;
foreach
(
$this
->
specs
as
$spec
)
{
if
(
$spec
->
getWildcard
(
)
)
{
$wildcard
=
$spec
;
break
;
}
}
foreach
(
$specs
as
$spec
)
{
$spec
->
validate
(
)
;
$name
=
$spec
->
getName
(
)
;
if
(
isset
(
$this
->
specs
[
$name
]
)
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'Two argument specifications have the same name ("%s").'
,
$name
)
)
;
}
$short
=
$spec
->
getShortAlias
(
)
;
if
(
$short
)
{
if
(
isset
(
$short_map
[
$short
]
)
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'Two argument specifications have the same short alias ("%s").'
,
$short
)
)
;
}
$short_map
[
$short
]
=
$spec
;
}
if
(
$spec
->
getWildcard
(
)
)
{
if
(
$wildcard
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'Two argument specifications are marked as wildcard arguments. '
.
'You can have a maximum of one wildcard argument.'
)
)
;
}
else
{
$wildcard
=
$spec
;
}
}
$this
->
specs
[
$name
]
=
$spec
;
}
foreach
(
$this
->
specs
as
$name
=>
$spec
)
{
foreach
(
$spec
->
getConflicts
(
)
as
$conflict
=>
$reason
)
{
if
(
empty
(
$this
->
specs
[
$conflict
]
)
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'Argument "%s" conflicts with unspecified argument "%s".'
,
$name
,
$conflict
)
)
;
}
if
(
$conflict
==
$name
)
{
throw
new
PhutilArgumentSpecificationException
(
pht
(
'Argument "%s" conflicts with itself!'
,
$name
)
)
;
}
}
}
}
private
function
renderArgumentSpecs
(
array
$specs
)
{
foreach
(
$specs
as
$key
=>
$spec
)
{
if
(
$spec
->
getWildcard
(
)
)
{
unset
(
$specs
[
$key
]
)
;
}
}
$out
=
array
(
)
;
$no_standard_options
=
!
empty
(
$this
->
specs
[
'show-standard-options'
]
)
&&
!
$this
->
getArg
(
'show-standard-options'
)
;
$specs
=
msort
(
$specs
,
'getName'
)
;
foreach
(
$specs
as
$spec
)
{
if
(
$spec
->
getStandard
(
)
&&
$no_standard_options
)
{
// If this is a standard argument and the user didn't pass
// --show-standard-options, skip it.
continue
;
}
$name
=
$this
->
indent
(
6
,
'__--%s__'
,
$spec
->
getName
(
)
)
;
$short
=
null
;
if
(
$spec
->
getShortAlias
(
)
)
{
$short
=
$this
->
format
(
', __-%s__'
,
$spec
->
getShortAlias
(
)
)
;
}
if
(
$spec
->
getParamName
(
)
)
{
$param
=
$this
->
format
(
' __%s__'
,
$spec
->
getParamName
(
)
)
;
$name
.=
$param
;
if
(
$short
)
{
$short
.=
$param
;
}
}
$out
[
]
=
$name
.
$short
;
$out
[
]
=
$this
->
indent
(
10
,
$spec
->
getHelp
(
)
)
;
$out
[
]
=
null
;
}
return
implode
(
"\n"
,
$out
)
;
}
private
function
format
(
$str
/* , ... */
)
{
$args
=
func_get_args
(
)
;
return
call_user_func_array
(
'phutil_console_format'
,
$args
)
;
}
private
function
indent
(
$level
,
$str
/* , ... */
)
{
$args
=
func_get_args
(
)
;
$args
=
array_slice
(
$args
,
1
)
;
$text
=
call_user_func_array
(
array
(
$this
,
'format'
)
,
$args
)
;
return
phutil_console_wrap
(
$text
,
$level
)
;
}
/**
* @phutil-external-symbol function xhprof_disable
*/
public
function
shutdownProfiler
(
)
{
$data
=
xhprof_disable
(
)
;
$data
=
json_encode
(
$data
)
;
Filesystem
::
writeFile
(
$this
->
getArg
(
'xprofile'
)
,
$data
)
;
}
public
static
function
isTraceModeEnabled
(
)
{
return
self
::
$traceModeEnabled
;
}
private
function
raiseUnknownWorkflow
(
$flow
,
array
$maybe
)
{
if
(
$maybe
)
{
sort
(
$maybe
)
;
$maybe_list
=
id
(
new
PhutilConsoleList
(
)
)
->
setWrap
(
false
)
->
setBullet
(
null
)
->
addItems
(
$maybe
)
->
drawConsoleString
(
)
;
$message
=
tsprintf
(
"%B\n%B"
,
pht
(
'Invalid command "%s". Did you mean:'
,
$flow
)
,
$maybe_list
)
;
}
else
{
$names
=
mpull
(
$this
->
workflows
,
'getName'
)
;
sort
(
$names
)
;
$message
=
tsprintf
(
'%B'
,
pht
(
'Invalid command "%s". Valid commands are: %s.'
,
$flow
,
implode
(
', '
,
$names
)
)
)
;
}
if
(
isset
(
$this
->
workflows
[
'help'
]
)
)
{
$binary
=
basename
(
$this
->
bin
)
;
$message
=
tsprintf
(
"%B\n%s"
,
$message
,
pht
(
'For details on available commands, run "%s".'
,
"{$binary} help"
)
)
;
}
throw
new
PhutilArgumentUsageException
(
$message
)
;
}
private
function
shouldAutocorrect
(
)
{
return
!
phutil_is_noninteractive
(
)
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Thu, Dec 19, 17:00 (21 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1014831
Default Alt Text
PhutilArgumentParser.php (28 KB)
Attached To
Mode
rARC Arcanist
Attached
Detach File
Event Timeline
Log In to Comment