| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
""" |
|---|
| 6 |
Rebuild the completion functions for the currently active version of Twisted:: |
|---|
| 7 |
$ python zshcomp.py -i |
|---|
| 8 |
|
|---|
| 9 |
This module implements a zsh code generator which generates completion code for |
|---|
| 10 |
commands that use twisted.python.usage. This is the stuff that makes pressing |
|---|
| 11 |
Tab at the command line work. |
|---|
| 12 |
|
|---|
| 13 |
Maintainer: Eric Mangold |
|---|
| 14 |
|
|---|
| 15 |
To build completion functions for your own commands, and not Twisted commands, |
|---|
| 16 |
then just do something like this:: |
|---|
| 17 |
|
|---|
| 18 |
o = mymodule.MyOptions() |
|---|
| 19 |
f = file('_mycommand', 'w') |
|---|
| 20 |
Builder("mycommand", o, f).write() |
|---|
| 21 |
|
|---|
| 22 |
Then all you have to do is place the generated file somewhere in your |
|---|
| 23 |
C{$fpath}, and restart zsh. Note the "site-functions" directory in your |
|---|
| 24 |
C{$fpath} where you may install 3rd-party completion functions (like the one |
|---|
| 25 |
you're building). Call C{siteFunctionsPath} to locate this directory |
|---|
| 26 |
programmatically. |
|---|
| 27 |
|
|---|
| 28 |
SPECIAL CLASS VARIABLES. You may set these on your usage.Options subclass:: |
|---|
| 29 |
|
|---|
| 30 |
zsh_altArgDescr |
|---|
| 31 |
zsh_multiUse |
|---|
| 32 |
zsh_mutuallyExclusive |
|---|
| 33 |
zsh_actions |
|---|
| 34 |
zsh_actionDescr |
|---|
| 35 |
zsh_extras |
|---|
| 36 |
|
|---|
| 37 |
Here is what they mean (with examples):: |
|---|
| 38 |
|
|---|
| 39 |
zsh_altArgDescr = {"foo":"use this description for foo instead"} |
|---|
| 40 |
A dict mapping long option names to alternate descriptions. When this |
|---|
| 41 |
variable is present, the descriptions contained here will override |
|---|
| 42 |
those descriptions provided in the optFlags and optParameters |
|---|
| 43 |
variables. |
|---|
| 44 |
|
|---|
| 45 |
zsh_multiUse = ["foo", "bar"] |
|---|
| 46 |
A sequence containing those long option names which may appear on the |
|---|
| 47 |
command line more than once. By default, options will only be completed |
|---|
| 48 |
one time. |
|---|
| 49 |
|
|---|
| 50 |
zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")] |
|---|
| 51 |
A sequence of sequences, with each sub-sequence containing those long |
|---|
| 52 |
option names that are mutually exclusive. That is, those options that |
|---|
| 53 |
cannot appear on the command line together. |
|---|
| 54 |
|
|---|
| 55 |
zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)", |
|---|
| 56 |
"colors":"_values -s , 'colors to use' red green blue"} |
|---|
| 57 |
A dict mapping long option names to Zsh "actions". These actions |
|---|
| 58 |
define what will be completed as the argument to the given option. By |
|---|
| 59 |
default, all files/dirs will be completed if no action is given. |
|---|
| 60 |
|
|---|
| 61 |
Callables may instead be given for the values in this dict. The |
|---|
| 62 |
callable should accept no arguments, and return a string that will be |
|---|
| 63 |
used as the zsh "action" in the same way as the literal strings in the |
|---|
| 64 |
examples above. |
|---|
| 65 |
|
|---|
| 66 |
As you can see in the example above. The "foo" option will have files |
|---|
| 67 |
that end in .foo completed when the user presses Tab. The "bar" |
|---|
| 68 |
option will have either of the strings "one", "two", or "three" |
|---|
| 69 |
completed when the user presses Tab. |
|---|
| 70 |
|
|---|
| 71 |
"colors" will allow multiple arguments to be completed, seperated by |
|---|
| 72 |
commas. The possible arguments are red, green, and blue. Examples:: |
|---|
| 73 |
|
|---|
| 74 |
my_command --foo some-file.foo --colors=red,green |
|---|
| 75 |
my_command --colors=green |
|---|
| 76 |
my_command --colors=green,blue |
|---|
| 77 |
|
|---|
| 78 |
Actions may take many forms, and it is beyond the scope of this |
|---|
| 79 |
document to illustrate them all. Please refer to the documention for |
|---|
| 80 |
the Zsh _arguments function. zshcomp is basically a front-end to Zsh's |
|---|
| 81 |
_arguments completion function. |
|---|
| 82 |
|
|---|
| 83 |
That documentation is available on the zsh web site at this URL: |
|---|
| 84 |
U{http://zsh.sunsite.dk/Doc/Release/zsh_19.html#SEC124} |
|---|
| 85 |
|
|---|
| 86 |
zsh_actionDescr = {"logfile":"log file name", "random":"random seed"} |
|---|
| 87 |
A dict mapping long option names to a description for the corresponding |
|---|
| 88 |
zsh "action". These descriptions are show above the generated matches |
|---|
| 89 |
when the user is doing completions for this option. |
|---|
| 90 |
|
|---|
| 91 |
Normally Zsh does not show these descriptions unless you have |
|---|
| 92 |
"verbose" completion turned on. Turn on verbosity with this in your |
|---|
| 93 |
~/.zshrc:: |
|---|
| 94 |
|
|---|
| 95 |
zstyle ':completion:*' verbose yes |
|---|
| 96 |
zstyle ':completion:*:descriptions' format '%B%d%b' |
|---|
| 97 |
|
|---|
| 98 |
zsh_extras = [":file to read from:action", ":file to write to:action"] |
|---|
| 99 |
A sequence of extra arguments that will be passed verbatim to Zsh's |
|---|
| 100 |
_arguments completion function. The _arguments function does all the |
|---|
| 101 |
hard work of doing command line completions. You can see how zshcomp |
|---|
| 102 |
invokes the _arguments call by looking at the generated completion |
|---|
| 103 |
files that this module creates. |
|---|
| 104 |
|
|---|
| 105 |
*** NOTE *** |
|---|
| 106 |
|
|---|
| 107 |
You will need to use this variable to describe completions for normal |
|---|
| 108 |
command line arguments. That is, those arguments that are not |
|---|
| 109 |
associated with an option. That is, the arguments that are given to the |
|---|
| 110 |
parseArgs method of your usage.Options subclass. |
|---|
| 111 |
|
|---|
| 112 |
In the example above, the 1st non-option argument will be described as |
|---|
| 113 |
"file to read from" and completion options will be generated in |
|---|
| 114 |
accordance with the "action". (See above about zsh "actions") The |
|---|
| 115 |
2nd non-option argument will be described as "file to write to" and |
|---|
| 116 |
the action will be interpreted likewise. |
|---|
| 117 |
|
|---|
| 118 |
Things you can put here are all documented under the _arguments |
|---|
| 119 |
function here: U{http://zsh.sunsite.dk/Doc/Release/zsh_19.html#SEC124} |
|---|
| 120 |
|
|---|
| 121 |
Zsh Notes: |
|---|
| 122 |
|
|---|
| 123 |
To enable advanced completion add something like this to your ~/.zshrc:: |
|---|
| 124 |
|
|---|
| 125 |
autoload -U compinit |
|---|
| 126 |
compinit |
|---|
| 127 |
|
|---|
| 128 |
For some extra verbosity, and general niceness add these lines too:: |
|---|
| 129 |
|
|---|
| 130 |
zstyle ':completion:*' verbose yes |
|---|
| 131 |
zstyle ':completion:*:descriptions' format '%B%d%b' |
|---|
| 132 |
zstyle ':completion:*:messages' format '%d' |
|---|
| 133 |
zstyle ':completion:*:warnings' format 'No matches for: %d' |
|---|
| 134 |
|
|---|
| 135 |
Have fun! |
|---|
| 136 |
""" |
|---|
| 137 |
import itertools, sys, commands, os.path |
|---|
| 138 |
|
|---|
| 139 |
from twisted.python import reflect, util, usage |
|---|
| 140 |
from twisted.scripts.mktap import IServiceMaker |
|---|
| 141 |
|
|---|
| 142 |
class MyOptions(usage.Options): |
|---|
| 143 |
""" |
|---|
| 144 |
Options for this file |
|---|
| 145 |
""" |
|---|
| 146 |
longdesc = "" |
|---|
| 147 |
synopsis = "Usage: python zshcomp.py [--install | -i] | <output directory>" |
|---|
| 148 |
optFlags = [["install", "i", |
|---|
| 149 |
'Output files to the "installation" directory ' \ |
|---|
| 150 |
'(twisted/python/zsh in the currently active ' \ |
|---|
| 151 |
'Twisted package)']] |
|---|
| 152 |
optParameters = [["directory", "d", None, |
|---|
| 153 |
"Output files to this directory"]] |
|---|
| 154 |
def postOptions(self): |
|---|
| 155 |
if self['install'] and self['directory']: |
|---|
| 156 |
raise usage.UsageError, "Can't have --install and " \ |
|---|
| 157 |
"--directory at the same time" |
|---|
| 158 |
if not self['install'] and not self['directory']: |
|---|
| 159 |
raise usage.UsageError, "Not enough arguments" |
|---|
| 160 |
if self['directory'] and not os.path.isdir(self['directory']): |
|---|
| 161 |
raise usage.UsageError, "%s is not a directory" % self['directory'] |
|---|
| 162 |
|
|---|
| 163 |
class Builder: |
|---|
| 164 |
def __init__(self, cmd_name, options, file): |
|---|
| 165 |
""" |
|---|
| 166 |
@type cmd_name: C{str} |
|---|
| 167 |
@param cmd_name: The name of the command |
|---|
| 168 |
|
|---|
| 169 |
@type options: C{twisted.usage.Options} |
|---|
| 170 |
@param options: The C{twisted.usage.Options} instance defined for |
|---|
| 171 |
this command |
|---|
| 172 |
|
|---|
| 173 |
@type file: C{file} |
|---|
| 174 |
@param file: The C{file} to write the completion function to |
|---|
| 175 |
""" |
|---|
| 176 |
|
|---|
| 177 |
self.cmd_name = cmd_name |
|---|
| 178 |
self.options = options |
|---|
| 179 |
self.file = file |
|---|
| 180 |
|
|---|
| 181 |
def write(self): |
|---|
| 182 |
""" |
|---|
| 183 |
Write the completion function to the file given to __init__ |
|---|
| 184 |
@return: C{None} |
|---|
| 185 |
""" |
|---|
| 186 |
|
|---|
| 187 |
self.file.write('#compdef %s\n' % (self.cmd_name,)) |
|---|
| 188 |
gen = ArgumentsGenerator(self.cmd_name, self.options, self.file) |
|---|
| 189 |
gen.write() |
|---|
| 190 |
|
|---|
| 191 |
class SubcommandBuilder(Builder): |
|---|
| 192 |
""" |
|---|
| 193 |
Use this builder for commands that have sub-commands. twisted.python.usage |
|---|
| 194 |
has the notion of sub-commands that are defined using an entirely seperate |
|---|
| 195 |
Options class. |
|---|
| 196 |
""" |
|---|
| 197 |
interface = None |
|---|
| 198 |
subcmdLabel = None |
|---|
| 199 |
|
|---|
| 200 |
def write(self): |
|---|
| 201 |
""" |
|---|
| 202 |
Write the completion function to the file given to __init__ |
|---|
| 203 |
@return: C{None} |
|---|
| 204 |
""" |
|---|
| 205 |
self.file.write('#compdef %s\n' % (self.cmd_name,)) |
|---|
| 206 |
self.file.write('local _zsh_subcmds_array\n_zsh_subcmds_array=(\n') |
|---|
| 207 |
from twisted import plugin as newplugin |
|---|
| 208 |
plugins = newplugin.getPlugins(self.interface) |
|---|
| 209 |
|
|---|
| 210 |
for p in plugins: |
|---|
| 211 |
self.file.write('"%s:%s"\n' % (p.tapname, p.description)) |
|---|
| 212 |
self.file.write(")\n\n") |
|---|
| 213 |
|
|---|
| 214 |
self.options.__class__.zsh_extras = ['*::subcmd:->subcmd'] |
|---|
| 215 |
gen = ArgumentsGenerator(self.cmd_name, self.options, self.file) |
|---|
| 216 |
gen.write() |
|---|
| 217 |
|
|---|
| 218 |
self.file.write("""if (( CURRENT == 1 )); then |
|---|
| 219 |
_describe "%s" _zsh_subcmds_array && ret=0 |
|---|
| 220 |
fi |
|---|
| 221 |
(( ret )) || return 0 |
|---|
| 222 |
|
|---|
| 223 |
service="$words[1]" |
|---|
| 224 |
|
|---|
| 225 |
case $service in\n""" % (self.subcmdLabel,)) |
|---|
| 226 |
|
|---|
| 227 |
plugins = newplugin.getPlugins(self.interface) |
|---|
| 228 |
for p in plugins: |
|---|
| 229 |
self.file.write(p.tapname + ")\n") |
|---|
| 230 |
gen = ArgumentsGenerator(p.tapname, p.options(), self.file) |
|---|
| 231 |
gen.write() |
|---|
| 232 |
self.file.write(";;\n") |
|---|
| 233 |
self.file.write("*) _message \"don't know how to" \ |
|---|
| 234 |
" complete $service\";;\nesac") |
|---|
| 235 |
|
|---|
| 236 |
class MktapBuilder(SubcommandBuilder): |
|---|
| 237 |
""" |
|---|
| 238 |
Builder for the mktap command |
|---|
| 239 |
""" |
|---|
| 240 |
interface = IServiceMaker |
|---|
| 241 |
subcmdLabel = 'tap to build' |
|---|
| 242 |
|
|---|
| 243 |
class TwistdBuilder(SubcommandBuilder): |
|---|
| 244 |
""" |
|---|
| 245 |
Builder for the twistd command |
|---|
| 246 |
""" |
|---|
| 247 |
interface = IServiceMaker |
|---|
| 248 |
subcmdLabel = 'service to run' |
|---|
| 249 |
|
|---|
| 250 |
class ArgumentsGenerator: |
|---|
| 251 |
""" |
|---|
| 252 |
Generate a call to the zsh _arguments completion function |
|---|
| 253 |
based on data in a usage.Options subclass |
|---|
| 254 |
""" |
|---|
| 255 |
def __init__(self, cmd_name, options, file): |
|---|
| 256 |
""" |
|---|
| 257 |
@type cmd_name: C{str} |
|---|
| 258 |
@param cmd_name: The name of the command |
|---|
| 259 |
|
|---|
| 260 |
@type options: C{twisted.usage.Options} |
|---|
| 261 |
@param options: The C{twisted.usage.Options} instance defined |
|---|
| 262 |
for this command |
|---|
| 263 |
|
|---|
| 264 |
@type file: C{file} |
|---|
| 265 |
@param file: The C{file} to write the completion function to |
|---|
| 266 |
""" |
|---|
| 267 |
self.cmd_name = cmd_name |
|---|
| 268 |
self.options = options |
|---|
| 269 |
self.file = file |
|---|
| 270 |
|
|---|
| 271 |
self.altArgDescr = {} |
|---|
| 272 |
self.actionDescr = {} |
|---|
| 273 |
self.multiUse = [] |
|---|
| 274 |
self.mutuallyExclusive = [] |
|---|
| 275 |
self.actions = {} |
|---|
| 276 |
self.extras = [] |
|---|
| 277 |
|
|---|
| 278 |
aCL = reflect.accumulateClassList |
|---|
| 279 |
aCD = reflect.accumulateClassDict |
|---|
| 280 |
|
|---|
| 281 |
aCD(options.__class__, 'zsh_altArgDescr', self.altArgDescr) |
|---|
| 282 |
aCD(options.__class__, 'zsh_actionDescr', self.actionDescr) |
|---|
| 283 |
aCL(options.__class__, 'zsh_multiUse', self.multiUse) |
|---|
| 284 |
aCL(options.__class__, 'zsh_mutuallyExclusive', |
|---|
| 285 |
self.mutuallyExclusive) |
|---|
| 286 |
aCD(options.__class__, 'zsh_actions', self.actions) |
|---|
| 287 |
aCL(options.__class__, 'zsh_extras', self.extras) |
|---|
| 288 |
|
|---|
| 289 |
optFlags = [] |
|---|
| 290 |
optParams = [] |
|---|
| 291 |
|
|---|
| 292 |
aCL(options.__class__, 'optFlags', optFlags) |
|---|
| 293 |
aCL(options.__class__, 'optParameters', optParams) |
|---|
| 294 |
|
|---|
| 295 |
for i, optList in enumerate(optFlags): |
|---|
| 296 |
if len(optList) != 3: |
|---|
| 297 |
optFlags[i] = util.padTo(3, optList) |
|---|
| 298 |
|
|---|
| 299 |
for i, optList in enumerate(optParams): |
|---|
| 300 |
if len(optList) != 4: |
|---|
| 301 |
optParams[i] = util.padTo(4, optList) |
|---|
| 302 |
|
|---|
| 303 |
|
|---|
| 304 |
self.optFlags = optFlags |
|---|
| 305 |
self.optParams = optParams |
|---|
| 306 |
|
|---|
| 307 |
optParams_d = {} |
|---|
| 308 |
for optList in optParams: |
|---|
| 309 |
optParams_d[optList[0]] = optList[1:] |
|---|
| 310 |
self.optParams_d = optParams_d |
|---|
| 311 |
|
|---|
| 312 |
optFlags_d = {} |
|---|
| 313 |
for optList in optFlags: |
|---|
| 314 |
optFlags_d[optList[0]] = optList[1:] |
|---|
| 315 |
self.optFlags_d = optFlags_d |
|---|
| 316 |
|
|---|
| 317 |
optAll_d = {} |
|---|
| 318 |
optAll_d.update(optParams_d) |
|---|
| 319 |
optAll_d.update(optFlags_d) |
|---|
| 320 |
self.optAll_d = optAll_d |
|---|
| 321 |
|
|---|
| 322 |
self.addAdditionalOptions() |
|---|
| 323 |
|
|---|
| 324 |
|
|---|
| 325 |
|
|---|
| 326 |
self.verifyZshNames() |
|---|
| 327 |
|
|---|
| 328 |
self.excludes = self.makeExcludesDict() |
|---|
| 329 |
|
|---|
| 330 |
def write(self): |
|---|
| 331 |
""" |
|---|
| 332 |
Write the zsh completion code to the file given to __init__ |
|---|
| 333 |
@return: C{None} |
|---|
| 334 |
""" |
|---|
| 335 |
self.writeHeader() |
|---|
| 336 |
self.writeExtras() |
|---|
| 337 |
self.writeOptions() |
|---|
| 338 |
self.writeFooter() |
|---|
| 339 |
|
|---|
| 340 |
def writeHeader(self): |
|---|
| 341 |
""" |
|---|
| 342 |
This is the start of the code that calls _arguments |
|---|
| 343 |
@return: C{None} |
|---|
| 344 |
""" |
|---|
| 345 |
self.file.write('_arguments -s -A "-*" \\\n') |
|---|
| 346 |
|
|---|
| 347 |
def writeOptions(self): |
|---|
| 348 |
""" |
|---|
| 349 |
Write out zsh code for each option in this command |
|---|
| 350 |
@return: C{None} |
|---|
| 351 |
""" |
|---|
| 352 |
optNames = self.optAll_d.keys() |
|---|
| 353 |
optNames.sort() |
|---|
| 354 |
for long in optNames: |
|---|
| 355 |
self.writeOpt(long) |
|---|
| 356 |
|
|---|
| 357 |
def writeExtras(self): |
|---|
| 358 |
""" |
|---|
| 359 |
Write out the "extras" list. These are just passed verbatim to the |
|---|
| 360 |
_arguments call |
|---|
| 361 |
@return: C{None} |
|---|
| 362 |
""" |
|---|
| 363 |
for s in self.extras: |
|---|
| 364 |
self.file.write(escape(s)) |
|---|
| 365 |
self.file.write(' \\\n') |
|---|
| 366 |
|
|---|
| 367 |
def writeFooter(self): |
|---|
| 368 |
""" |
|---|
| 369 |
Write the last bit of code that finishes the call to _arguments |
|---|
| 370 |
@return: C{None} |
|---|
| 371 |
""" |
|---|
| 372 |
self.file.write('&& return 0\n') |
|---|
| 373 |
|
|---|
| 374 |
def verifyZshNames(self): |
|---|
| 375 |
""" |
|---|
| 376 |
Ensure that none of the names given in zsh_* variables are typoed |
|---|
| 377 |
@return: C{None} |
|---|
| 378 |
@raise ValueError: Raised if unknown option names have been given in |
|---|
| 379 |
zsh_* variables |
|---|
| 380 |
""" |
|---|
| 381 |
def err(name): |
|---|
| 382 |
raise ValueError, "Unknown option name \"%s\" found while\n" \ |
|---|
| 383 |
"examining zsh_ attributes for the %s command" % ( |
|---|
| 384 |
name, self.cmd_name) |
|---|
| 385 |
|
|---|
| 386 |
for name in itertools.chain(self.altArgDescr, self.actionDescr, |
|---|
| 387 |
self.actions, self.multiUse): |
|---|
| 388 |
if name not in self.optAll_d: |
|---|
| 389 |
err(name) |
|---|
| 390 |
|
|---|
| 391 |
for seq in self.mutuallyExclusive: |
|---|
| 392 |
for name in seq: |
|---|
| 393 |
if name not in self.optAll_d: |
|---|
| 394 |
err(name) |
|---|
| 395 |
|
|---|
| 396 |
def excludeStr(self, long, buildShort=False): |
|---|
| 397 |
""" |
|---|
| 398 |
Generate an "exclusion string" for the given option |
|---|
| 399 |
|
|---|
| 400 |
@type long: C{str} |
|---|
| 401 |
@param long: The long name of the option |
|---|
| 402 |
(i.e. "verbose" instead of "v") |
|---|
| 403 |
|
|---|
| 404 |
@type buildShort: C{bool} |
|---|
| 405 |
@param buildShort: May be True to indicate we're building an excludes |
|---|
| 406 |
string for the short option that correspondes to |
|---|
| 407 |
the given long opt |
|---|
| 408 |
|
|---|
| 409 |
@return: The generated C{str} |
|---|
| 410 |
""" |
|---|
| 411 |
if long in self.excludes: |
|---|
| 412 |
exclusions = self.excludes[long][:] |
|---|
| 413 |
else: |
|---|
| 414 |
exclusions = [] |
|---|
| 415 |
|
|---|
| 416 |
|
|---|
| 417 |
|
|---|
| 418 |
|
|---|
| 419 |
if long not in self.multiUse: |
|---|
| 420 |
if buildShort is False: |
|---|
| 421 |
short = self.getShortOption(long) |
|---|
| 422 |
if short is not None: |
|---|
| 423 |
exclusions.append(short) |
|---|
| 424 |
else: |
|---|
| 425 |
exclusions.append(long) |
|---|
| 426 |
|
|---|
| 427 |
if not exclusions: |
|---|
| 428 |
return '' |
|---|
| 429 |
|
|---|
| 430 |
strings = [] |
|---|
| 431 |
for optName in exclusions: |
|---|
| 432 |
if len(optName) == 1: |
|---|
| 433 |
|
|---|
| 434 |
strings.append("-" + optName) |
|---|
| 435 |
else: |
|---|
| 436 |
strings.append("--" + optName) |
|---|
| 437 |
return "(%s)" % " ".join(strings) |
|---|
| 438 |
|
|---|
| 439 |
def makeExcludesDict(self): |
|---|
| 440 |
""" |
|---|
| 441 |
@return: A C{dict} that maps each option name appearing in |
|---|
| 442 |
self.mutuallyExclusive to a list of those option names that |
|---|
| 443 |
is it mutually exclusive with (can't appear on the cmd line with) |
|---|
| 444 |
""" |
|---|
| 445 |
|
|---|
| 446 |
|
|---|
| 447 |
longToShort = {} |
|---|
| 448 |
for optList in itertools.chain(self.optParams, self.optFlags): |
|---|
| 449 |
try: |
|---|
| 450 |
if optList[1] != None: |
|---|
| 451 |
longToShort[optList[0]] = optList[1] |
|---|
| 452 |
except IndexError: |
|---|
| 453 |
pass |
|---|
| 454 |
|
|---|
| 455 |
excludes = {} |
|---|
| 456 |
for lst in self.mutuallyExclusive: |
|---|
| 457 |
for i, long in enumerate(lst): |
|---|
| 458 |
tmp = [] |
|---|
| 459 |
tmp.extend(lst[:i]) |
|---|
| 460 |
tmp.extend(lst[i+1:]) |
|---|
| 461 |
for name in tmp[:]: |
|---|
| 462 |
if name in longToShort: |
|---|
| 463 |
tmp.append(longToShort[name]) |
|---|
| 464 |
|
|---|
| 465 |
if long in excludes: |
|---|
| 466 |
excludes[long].extend(tmp) |
|---|
| 467 |
else: |
|---|
| 468 |
excludes[long] = tmp |
|---|
| 469 |
return excludes |
|---|
| 470 |
|
|---|
| 471 |
def writeOpt(self, long): |
|---|
| 472 |
""" |
|---|
| 473 |
Write out the zsh code for the given argument. This is just part of the |
|---|
| 474 |
one big call to _arguments |
|---|
| 475 |
|
|---|
| 476 |
@type long: C{str} |
|---|
| 477 |
@param long: The long name of the option |
|---|
| 478 |
(i.e. "verbose" instead of "v") |
|---|
| 479 |
|
|---|
| 480 |
@return: C{None} |
|---|
| 481 |
""" |
|---|
| 482 |
if long in self.optFlags_d: |
|---|
| 483 |
|
|---|
| 484 |
long_field = "--%s" % long |
|---|
| 485 |
else: |
|---|
| 486 |
long_field = "--%s=" % long |
|---|
| 487 |
|
|---|
| 488 |
short = self.getShortOption(long) |
|---|
| 489 |
if short != None: |
|---|
| 490 |
short_field = "-" + short |
|---|
| 491 |
else: |
|---|
| 492 |
short_field = '' |
|---|
| 493 |
|
|---|
| 494 |
descr = self.getDescription(long) |
|---|
| 495 |
descr_field = descr.replace("[", "\[") |
|---|
| 496 |
descr_field = descr_field.replace("]", "\]") |
|---|
| 497 |
descr_field = '[%s]' % descr_field |
|---|
| 498 |
|
|---|
| 499 |
if long in self.actionDescr: |
|---|
| 500 |
actionDescr_field = self.actionDescr[long] |
|---|
| 501 |
else: |
|---|
| 502 |
actionDescr_field = descr |
|---|
| 503 |
|
|---|
| 504 |
action_field = self.getAction(long) |
|---|
| 505 |
if long in self.multiUse: |
|---|
| 506 |
multi_field = '*' |
|---|
| 507 |
else: |
|---|
| 508 |
multi_field = '' |
|---|
| 509 |
|
|---|
| 510 |
longExclusions_field = self.excludeStr(long) |
|---|
| 511 |
|
|---|
| 512 |
if short: |
|---|
| 513 |
|
|---|
| 514 |
shortExclusions_field = self.excludeStr(long, buildShort=True) |
|---|
| 515 |
self.file.write(escape('%s%s%s%s%s' % (shortExclusions_field, |
|---|
| 516 |
multi_field, short_field, descr_field, action_field))) |
|---|
| 517 |
self.file.write(' \\\n') |
|---|
| 518 |
|
|---|
| 519 |
self.file.write(escape('%s%s%s%s%s' % (longExclusions_field, |
|---|
| 520 |
multi_field, long_field, descr_field, action_field))) |
|---|
| 521 |
self.file.write(' \\\n') |
|---|
| 522 |
|
|---|
| 523 |
def getAction(self, long): |
|---|
| 524 |
""" |
|---|
| 525 |
Return a zsh "action" string for the given argument |
|---|
| 526 |
@return: C{str} |
|---|
| 527 |
""" |
|---|
| 528 |
if long in self.actions: |
|---|
| 529 |
if callable(self.actions[long]): |
|---|
| 530 |
action = self.actions[long]() |
|---|
| 531 |
else: |
|---|
| 532 |
action = self.actions[long] |
|---|
| 533 |
return ":%s:%s" % (self.getActionDescr(long), action) |
|---|
| 534 |
if long in self.optParams_d: |
|---|
| 535 |
return ':%s:_files' % self.getActionDescr(long) |
|---|
| 536 |
return '' |
|---|
| 537 |
|
|---|
| 538 |
def getActionDescr(self, long): |
|---|
| 539 |
""" |
|---|
| 540 |
Return the description to be used when this argument is completed |
|---|
| 541 |
@return: C{str} |
|---|
| 542 |
""" |
|---|
| 543 |
if long in self.actionDescr: |
|---|
| 544 |
return self.actionDescr[long] |
|---|
| 545 |
else: |
|---|
| 546 |
return long |
|---|
| 547 |
|
|---|
| 548 |
def getDescription(self, long): |
|---|
| 549 |
""" |
|---|
| 550 |
Return the description to be used for this argument |
|---|
| 551 |
@return: C{str} |
|---|
| 552 |
""" |
|---|
| 553 |
|
|---|
| 554 |
if long in self.altArgDescr: |
|---|
| 555 |
return self.altArgDescr[long] |
|---|
| 556 |
|
|---|
| 557 |
|
|---|
| 558 |
try: |
|---|
| 559 |
descr = self.optFlags_d[long][1] |
|---|
| 560 |
except KeyError: |
|---|
| 561 |
try: |
|---|
| 562 |
descr = self.optParams_d[long][2] |
|---|
| 563 |
except KeyError: |
|---|
| 564 |
descr = None |
|---|
| 565 |
|
|---|
| 566 |
if descr is not None: |
|---|
| 567 |
return descr |
|---|
| 568 |
|
|---|
| 569 |
|
|---|
| 570 |
longMangled = long.replace('-', '_') |
|---|
| 571 |
obj = getattr(self.options, 'opt_%s' % longMangled, None) |
|---|
| 572 |
if obj: |
|---|
| 573 |
descr = descrFromDoc(obj) |
|---|
| 574 |
if descr is not None: |
|---|
| 575 |
return descr |
|---|
| 576 |
|
|---|
| 577 |
return long |
|---|
| 578 |
|
|---|
| 579 |
def getShortOption(self, long): |
|---|
| 580 |
""" |
|---|
| 581 |
Return the short option letter or None |
|---|
| 582 |
@return: C{str} or C{None} |
|---|
| 583 |
""" |
|---|
| 584 |
optList = self.optAll_d[long] |
|---|
| 585 |
try: |
|---|
| 586 |
return optList[0] or None |
|---|
| 587 |
except IndexError: |
|---|
| 588 |
pass |
|---|
| 589 |
|
|---|
| 590 |
def addAdditionalOptions(self): |
|---|
| 591 |
""" |
|---|
| 592 |
Add additional options to the optFlags and optParams lists. |
|---|
| 593 |
These will be defined by 'opt_foo' methods of the Options subclass |
|---|
| 594 |
@return: C{None} |
|---|
| 595 |
""" |
|---|
| 596 |
methodsDict = {} |
|---|
| 597 |
reflect.accumulateMethods(self.options, methodsDict, 'opt_') |
|---|
| 598 |
methodToShort = {} |
|---|
| 599 |
for name in methodsDict.copy(): |
|---|
| 600 |
if len(name) == 1: |
|---|
| 601 |
methodToShort[methodsDict[name]] = name |
|---|
| 602 |
del methodsDict[name] |
|---|
| 603 |
|
|---|
| 604 |
for methodName, methodObj in methodsDict.items(): |
|---|
| 605 |
long = methodName.replace('_', '-') |
|---|
| 606 |
|
|---|
| 607 |
|
|---|
| 608 |
if long in self.optAll_d: |
|---|
| 609 |
continue |
|---|
| 610 |
|
|---|
| 611 |
descr = self.getDescription(long) |
|---|
| 612 |
|
|---|
| 613 |
short = None |
|---|
| 614 |
if methodObj in methodToShort: |
|---|
| 615 |
short = methodToShort[methodObj] |
|---|
| 616 |
|
|---|
| 617 |
reqArgs = methodObj.im_func.func_code.co_argcount |
|---|
| 618 |
if reqArgs == 2: |
|---|
| 619 |
self.optParams.append([long, short, None, descr]) |
|---|
| 620 |
self.optParams_d[long] = [short, None, descr] |
|---|
| 621 |
self.optAll_d[long] = [short, None, descr] |
|---|
| 622 |
elif reqArgs == 1: |
|---|
| 623 |
self.optFlags.append([long, short, descr]) |
|---|
| 624 |
self.optFlags_d[long] = [short, descr] |
|---|
| 625 |
self.optAll_d[long] = [short, None, descr] |
|---|
| 626 |
else: |
|---|
| 627 |
raise TypeError, '%r has wrong number ' \ |
|---|
| 628 |
'of arguments' % (methodObj,) |
|---|
| 629 |
|
|---|
| 630 |
def descrFromDoc(obj): |
|---|
| 631 |
""" |
|---|
| 632 |
Generate an appropriate description from docstring of the given object |
|---|
| 633 |
""" |
|---|
| 634 |
if obj.__doc__ is None: |
|---|
| 635 |
return None |
|---|
| 636 |
|
|---|
| 637 |
lines = obj.__doc__.split("\n") |
|---|
| 638 |
descr = None |
|---|
| 639 |
try: |
|---|
| 640 |
if lines[0] != "" and not lines[0].isspace(): |
|---|
| 641 |
descr = lines[0].lstrip() |
|---|
| 642 |
|
|---|
| 643 |
elif lines[1] != "" and not lines[1].isspace(): |
|---|
| 644 |
descr = lines[1].lstrip() |
|---|
| 645 |
except IndexError: |
|---|
| 646 |
pass |
|---|
| 647 |
return descr |
|---|
| 648 |
|
|---|
| 649 |
def firstLine(s): |
|---|
| 650 |
""" |
|---|
| 651 |
Return the first line of the given string |
|---|
| 652 |
""" |
|---|
| 653 |
try: |
|---|
| 654 |
i = s.index('\n') |
|---|
| 655 |
return s[:i] |
|---|
| 656 |
except ValueError: |
|---|
| 657 |
return s |
|---|
| 658 |
|
|---|
| 659 |
def escape(str): |
|---|
| 660 |
""" |
|---|
| 661 |
Shell escape the given string |
|---|
| 662 |
""" |
|---|
| 663 |
return commands.mkarg(str)[1:] |
|---|
| 664 |
|
|---|
| 665 |
def siteFunctionsPath(): |
|---|
| 666 |
""" |
|---|
| 667 |
Return the path to the system-wide site-functions directory or |
|---|
| 668 |
C{None} if it cannot be determined |
|---|
| 669 |
""" |
|---|
| 670 |
try: |
|---|
| 671 |
cmd = "zsh -f -c 'echo ${(M)fpath:#/*/site-functions}'" |
|---|
| 672 |
output = commands.getoutput(cmd) |
|---|
| 673 |
if os.path.isdir(output): |
|---|
| 674 |
return output |
|---|
| 675 |
except: |
|---|
| 676 |
pass |
|---|
| 677 |
|
|---|
| 678 |
generateFor = [('conch', 'twisted.conch.scripts.conch', 'ClientOptions'), |
|---|
| 679 |
('mktap', 'twisted.scripts.mktap', 'FirstPassOptions'), |
|---|
| 680 |
('trial', 'twisted.scripts.trial', 'Options'), |
|---|
| 681 |
('cftp', 'twisted.conch.scripts.cftp', 'ClientOptions'), |
|---|
| 682 |
('tapconvert', 'twisted.scripts.tapconvert', 'ConvertOptions'), |
|---|
| 683 |
('twistd', 'twisted.scripts.twistd', 'ServerOptions'), |
|---|
| 684 |
('ckeygen', 'twisted.conch.scripts.ckeygen', 'GeneralOptions'), |
|---|
| 685 |
('lore', 'twisted.lore.scripts.lore', 'Options'), |
|---|
| 686 |
('pyhtmlizer', 'twisted.scripts.htmlizer', 'Options'), |
|---|
| 687 |
('tap2deb', 'twisted.scripts.tap2deb', 'MyOptions'), |
|---|
| 688 |
('tkconch', 'twisted.conch.scripts.tkconch', 'GeneralOptions'), |
|---|
| 689 |
('manhole', 'twisted.scripts.manhole', 'MyOptions'), |
|---|
| 690 |
('tap2rpm', 'twisted.scripts.tap2rpm', 'MyOptions'), |
|---|
| 691 |
('websetroot', None, None), |
|---|
| 692 |
('tkmktap', None, None), |
|---|
| 693 |
] |
|---|
| 694 |
|
|---|
| 695 |
|
|---|
| 696 |
|
|---|
| 697 |
|
|---|
| 698 |
|
|---|
| 699 |
|
|---|
| 700 |
|
|---|
| 701 |
specialBuilders = {'mktap' : MktapBuilder, |
|---|
| 702 |
'twistd' : TwistdBuilder} |
|---|
| 703 |
|
|---|
| 704 |
def makeCompFunctionFiles(out_path, generateFor=generateFor, |
|---|
| 705 |
specialBuilders=specialBuilders): |
|---|
| 706 |
""" |
|---|
| 707 |
Generate completion function files in the given directory for all |
|---|
| 708 |
twisted commands |
|---|
| 709 |
|
|---|
| 710 |
@type out_path: C{str} |
|---|
| 711 |
@param out_path: The path to the directory to generate completion function |
|---|
| 712 |
fils in |
|---|
| 713 |
|
|---|
| 714 |
@param generateFor: Sequence in the form of the 'generateFor' top-level |
|---|
| 715 |
variable as defined in this module. Indicates what |
|---|
| 716 |
commands to build completion files for. |
|---|
| 717 |
|
|---|
| 718 |
@param specialBuilders: Sequence in the form of the 'specialBuilders' |
|---|
| 719 |
top-level variable as defined in this module. |
|---|
| 720 |
Indicates what commands require a special |
|---|
| 721 |
Builder class. |
|---|
| 722 |
|
|---|
| 723 |
@return: C{list} of 2-tuples of the form (cmd_name, error) indicating |
|---|
| 724 |
commands that we skipped building completions for. cmd_name |
|---|
| 725 |
is the name of the skipped command, and error is the Exception |
|---|
| 726 |
that was raised when trying to import the script module. |
|---|
| 727 |
Commands are usually skipped due to a missing dependency, |
|---|
| 728 |
e.g. Tkinter. |
|---|
| 729 |
""" |
|---|
| 730 |
skips = [] |
|---|
| 731 |
for cmd_name, module_name, class_name in generateFor: |
|---|
| 732 |
if module_name is None: |
|---|
| 733 |
|
|---|
| 734 |
f = _openCmdFile(out_path, cmd_name) |
|---|
| 735 |
f.close() |
|---|
| 736 |
continue |
|---|
| 737 |
try: |
|---|
| 738 |
m = __import__('%s' % (module_name,), None, None, (class_name)) |
|---|
| 739 |
f = _openCmdFile(out_path, cmd_name) |
|---|
| 740 |
o = getattr(m, class_name)() |
|---|
| 741 |
|
|---|
| 742 |
if cmd_name in specialBuilders: |
|---|
| 743 |
b = specialBuilders[cmd_name](cmd_name, o, f) |
|---|
| 744 |
b.write() |
|---|
| 745 |
else: |
|---|
| 746 |
b = Builder(cmd_name, o, f) |
|---|
| 747 |
b.write() |
|---|
| 748 |
except Exception, e: |
|---|
| 749 |
skips.append( (cmd_name, e) ) |
|---|
| 750 |
continue |
|---|
| 751 |
return skips |
|---|
| 752 |
|
|---|
| 753 |
def _openCmdFile(out_path, cmd_name): |
|---|
| 754 |
return file(os.path.join(out_path, '_'+cmd_name), 'w') |
|---|
| 755 |
|
|---|
| 756 |
def run(): |
|---|
| 757 |
options = MyOptions() |
|---|
| 758 |
try: |
|---|
| 759 |
options.parseOptions(sys.argv[1:]) |
|---|
| 760 |
except usage.UsageError, e: |
|---|
| 761 |
print e |
|---|
| 762 |
print options.getUsage() |
|---|
| 763 |
sys.exit(2) |
|---|
| 764 |
|
|---|
| 765 |
if options['install']: |
|---|
| 766 |
import twisted |
|---|
| 767 |
dir = os.path.join(os.path.dirname(twisted.__file__), "python", "zsh") |
|---|
| 768 |
skips = makeCompFunctionFiles(dir) |
|---|
| 769 |
else: |
|---|
| 770 |
skips = makeCompFunctionFiles(options['directory']) |
|---|
| 771 |
|
|---|
| 772 |
for cmd_name, error in skips: |
|---|
| 773 |
sys.stderr.write("zshcomp: Skipped building for %s. Script module " \ |
|---|
| 774 |
"could not be imported:\n" % (cmd_name,)) |
|---|
| 775 |
sys.stderr.write(str(error)+'\n') |
|---|
| 776 |
if skips: |
|---|
| 777 |
sys.exit(3) |
|---|
| 778 |
|
|---|
| 779 |
if __name__ == '__main__': |
|---|
| 780 |
run() |
|---|