8 from plugin
import YapPlugin
11 class ShellError(Exception):
12 def __init__(self
, cmd
, rc
):
17 return "%s returned %d" % (self
.cmd
, self
.rc
)
19 class YapError(Exception):
20 def __init__(self
, msg
):
29 plugindir
= os
.path
.expanduser("~/.yap/plugins")
30 for p
in glob
.glob(os
.path
.join(plugindir
, "*.py")):
33 for cls
in glbls
.values():
34 if not type(cls
) == type:
36 if not issubclass(cls
, YapPlugin
):
41 # XXX: check for override overlap
44 def _add_new_file(self
, file):
45 repo
= get_output('git rev-parse --git-dir')[0]
46 dir = os
.path
.join(repo
, 'yap')
51 files
= self
._get
_new
_files
()
53 path
= os
.path
.join(dir, 'new-files')
54 pickle
.dump(files
, open(path
, 'w'))
56 def _get_new_files(self
):
57 repo
= get_output('git rev-parse --git-dir')[0]
58 path
= os
.path
.join(repo
, 'yap', 'new-files')
60 files
= pickle
.load(file(path
))
67 if get_output("git ls-files --cached '%s'" % f
) != []:
72 def _remove_new_file(self
, file):
73 files
= self
._get
_new
_files
()
74 files
= filter(lambda x
: x
!= file, files
)
76 repo
= get_output('git rev-parse --git-dir')[0]
77 path
= os
.path
.join(repo
, 'yap', 'new-files')
78 pickle
.dump(files
, open(path
, 'w'))
80 def _clear_new_files(self
):
81 repo
= get_output('git rev-parse --git-dir')[0]
82 path
= os
.path
.join(repo
, 'yap', 'new-files')
85 def _assert_file_exists(self
, file):
86 if not os
.access(file, os
.R_OK
):
87 raise YapError("No such file: %s" % file)
89 def _get_staged_files(self
):
90 if run_command("git rev-parse HEAD"):
91 files
= get_output("git ls-files --cached")
93 files
= get_output("git diff-index --cached --name-only HEAD")
96 def _get_unstaged_files(self
):
97 files
= self
._get
_new
_files
()
98 files
+= get_output("git ls-files -m")
101 def _delete_branch(self
, branch
, force
):
102 current
= get_output("git symbolic-ref HEAD")[0]
103 current
= current
.replace('refs/heads/', '')
104 if branch
== current
:
105 raise YapError("Can't delete current branch")
107 ref
= get_output("git rev-parse --verify 'refs/heads/%s'" % branch
)
109 raise YapError("No such branch: %s" % branch
)
110 run_safely("git update-ref -d 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
113 name
= get_output("git name-rev --name-only '%s'" % ref
[0])[0]
114 if name
== 'undefined':
115 run_command("git update-ref 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
116 raise YapError("Refusing to delete leaf branch (use -f to force)")
117 def _get_pager_cmd(self
):
118 if 'YAP_PAGER' in os
.environ
:
119 return os
.environ
['YAP_PAGER']
120 elif 'GIT_PAGER' in os
.environ
:
121 return os
.environ
['GIT_PAGER']
122 elif 'PAGER' in os
.environ
:
123 return os
.environ
['PAGER']
127 def _add_one(self
, file):
128 self
._assert
_file
_exists
(file)
129 x
= get_output("git ls-files '%s'" % file)
131 raise YapError("File '%s' already in repository" % file)
132 self
._add
_new
_file
(file)
134 def _rm_one(self
, file):
135 self
._assert
_file
_exists
(file)
136 if get_output("git ls-files '%s'" % file) != []:
137 run_safely("git rm --cached '%s'" % file)
138 self
._remove
_new
_file
(file)
140 def _stage_one(self
, file):
141 self
._assert
_file
_exists
(file)
142 run_safely("git update-index --add '%s'" % file)
144 def _unstage_one(self
, file):
145 self
._assert
_file
_exists
(file)
146 if run_command("git rev-parse HEAD"):
147 run_safely("git update-index --force-remove '%s'" % file)
149 run_safely("git diff-index -p HEAD '%s' | git apply -R --cached" % file)
151 def _revert_one(self
, file):
152 self
._assert
_file
_exists
(file)
153 self
._unstage
_one
(file)
154 run_safely("git checkout-index -u -f '%s'" % file)
156 def _parse_commit(self
, commit
):
157 lines
= get_output("git cat-file commit '%s'" % commit
)
162 if mode
!= 'commit' and l
.strip() == "":
167 commit
['log'].append(l
)
174 commit
['log'] = '\n'.join(commit
['log'])
177 def _check_commit(self
, **flags
):
178 if '-a' in flags
and '-d' in flags
:
179 raise YapError("Conflicting flags: -a and -d")
181 if '-d' not in flags
and self
._get
_unstaged
_files
():
182 if '-a' not in flags
and self
._get
_staged
_files
():
183 raise YapError("Staged and unstaged changes present. Specify what to commit")
184 os
.system("git diff-files -p | git apply --cached")
185 for f
in self
._get
_new
_files
():
188 def _do_uncommit(self
):
189 commit
= self
._parse
_commit
("HEAD")
190 repo
= get_output('git rev-parse --git-dir')[0]
191 dir = os
.path
.join(repo
, 'yap')
196 msg_file
= os
.path
.join(dir, 'msg')
197 fd
= file(msg_file
, 'w')
198 print >>fd
, commit
['log']
201 tree
= get_output("git rev-parse --verify HEAD^")
202 run_safely("git update-ref -m uncommit HEAD '%s'" % tree
[0])
204 def _do_commit(self
, msg
=None):
205 tree
= get_output("git write-tree")[0]
206 parent
= get_output("git rev-parse --verify HEAD 2> /dev/null")[0]
208 if os
.environ
.has_key('YAP_EDITOR'):
209 editor
= os
.environ
['YAP_EDITOR']
210 elif os
.environ
.has_key('GIT_EDITOR'):
211 editor
= os
.environ
['GIT_EDITOR']
212 elif os
.environ
.has_key('EDITOR'):
213 editor
= os
.environ
['EDITOR']
217 fd
, tmpfile
= tempfile
.mkstemp("yap")
220 repo
= get_output('git rev-parse --git-dir')[0]
221 msg_file
= os
.path
.join(repo
, 'yap', 'msg')
222 if os
.access(msg_file
, os
.R_OK
):
224 fd2
= file(tmpfile
, 'w')
225 for l
in fd1
.xreadlines():
226 print >>fd2
, l
.strip()
231 fd
= file(tmpfile
, 'w')
234 elif os
.system("%s '%s'" % (editor
, tmpfile
)) != 0:
235 raise YapError("Editing commit message failed")
237 commit
= get_output("git commit-tree '%s' -p '%s' < '%s'" % (tree
, parent
, tmpfile
))
239 commit
= get_output("git commit-tree '%s' < '%s'" % (tree
, tmpfile
))
241 raise YapError("Commit failed; no log message?")
243 run_safely("git update-ref HEAD '%s'" % commit
[0])
245 def _check_rebasing(self
):
246 repo
= get_output('git rev-parse --git-dir')[0]
247 dotest
= os
.path
.join(repo
, '.dotest')
248 if os
.access(dotest
, os
.R_OK
):
249 raise YapError("A git operation is in progress. Complete it first")
250 dotest
= os
.path
.join(repo
, '..', '.dotest')
251 if os
.access(dotest
, os
.R_OK
):
252 raise YapError("A git operation is in progress. Complete it first")
254 def _list_remotes(self
):
255 remotes
= get_output("git config --get-regexp '^remote.*.url'")
257 remote
, url
= x
.split(' ')
258 remote
= remote
.replace('remote.', '')
259 remote
= remote
.replace('.url', '')
262 @short_help("make a local copy of an existing repository")
264 The first argument is a URL to the existing repository. This can be an
265 absolute path if the repository is local, or a URL with the git://,
266 ssh://, or http:// schemes. By default, the directory used is the last
267 component of the URL, sans '.git'. This can be overridden by providing
270 def cmd_clone(self
, url
, directory
=""):
272 # XXX: implement in terms of init + remote add + fetch
273 os
.system("git clone '%s' %s" % (url
, directory
))
275 @short_help("turn a directory into a repository")
277 Converts the current working directory into a repository. The primary
278 side-effect of this command is the creation of a '.git' subdirectory.
279 No files are added nor commits made.
282 os
.system("git init")
284 @short_help("add a new file to the repository")
286 The arguments are the files to be added to the repository. Once added,
287 the files will show as "unstaged changes" in the output of 'status'. To
288 reverse the effects of this command, see 'rm'.
290 def cmd_add(self
, *files
):
299 @short_help("delete a file from the repository")
301 The arguments are the files to be removed from the current revision of
302 the repository. The files will still exist in any past commits that the
303 files may have been a part of. The file is not actually deleted, it is
304 just no longer tracked as part of the repository.
306 def cmd_rm(self
, *files
):
315 @short_help("stage changes in a file for commit")
317 The arguments are the files to be staged. Staging changes is a way to
318 build up a commit when you do not want to commit all changes at once.
319 To commit only staged changes, use the '-d' flag to 'commit.' To
320 reverse the effects of this command, see 'unstage'. Once staged, the
321 files will show as "staged changes" in the output of 'status'.
323 def cmd_stage(self
, *files
):
332 @short_help("unstage changes in a file")
334 The arguments are the files to be unstaged. Once unstaged, the files
335 will show as "unstaged changes" in the output of 'status'. The '-a'
336 flag can be used to unstage all staged changes at once.
339 def cmd_unstage(self
, *files
, **flags
):
343 run_safely("git read-tree -m HEAD")
345 run_safely("git read-tree HEAD")
346 run_safely("git update-index -q --refresh")
357 @short_help("show files with staged and unstaged changes")
359 Show the files in the repository with changes since the last commit,
360 categorized based on whether the changes are staged or not. A file may
361 appear under each heading if the same file has both staged and unstaged
364 def cmd_status(self
):
366 branch
= get_output("git symbolic-ref HEAD")[0]
367 branch
= branch
.replace('refs/heads/', '')
368 print "Current branch: %s" % branch
370 print "Files with staged changes:"
371 files
= self
._get
_staged
_files
()
377 print "Files with unstaged changes:"
378 prefix
= get_output("git rev-parse --show-prefix")
379 files
= self
._get
_unstaged
_files
()
382 f
= os
.path
.join(prefix
[0], f
)
387 @short_help("remove uncommitted changes from a file (*)")
389 The arguments are the files whose changes will be reverted. If the '-a'
390 flag is given, then all files will have uncommitted changes removed.
391 Note that there is no way to reverse this command short of manually
392 editing each file again.
395 def cmd_revert(self
, *files
, **flags
):
398 run_safely("git read-tree -u -m HEAD")
399 run_safely("git checkout-index -u -f -a")
410 @short_help("record changes to files as a new commit")
412 Create a new commit recording changes since the last commit. If there
413 are only unstaged changes, those will be recorded. If there are only
414 staged changes, those will be recorded. Otherwise, you will have to
415 specify either the '-a' flag or the '-d' flag to commit all changes or
416 only staged changes, respectively. To reverse the effects of this
417 command, see 'uncommit'.
419 @takes_options("adm:")
420 def cmd_commit(self
, **flags
):
422 self
._check
_rebasing
()
423 self
._check
_commit
(**flags
)
424 if not self
._get
_staged
_files
():
425 raise YapError("No changes to commit")
426 msg
= flags
.get('-m', None)
430 @short_help("reverse the actions of the last commit")
432 Reverse the effects of the last 'commit' operation. The changes that
433 were part of the previous commit will show as "staged changes" in the
434 output of 'status'. This means that if no files were changed since the
435 last commit was created, 'uncommit' followed by 'commit' is a lossless
438 def cmd_uncommit(self
):
443 @short_help("report the current version of yap")
444 def cmd_version(self
):
445 print "Yap version 0.1"
447 @short_help("show the changelog for particular versions or files")
449 The arguments are the files with which to filter history. If none are
450 given, all changes are listed. Otherwise only commits that affected one
451 or more of the given files are listed. The -r option changes the
452 starting revision for traversing history. By default, history is listed
456 def cmd_log(self
, *paths
, **flags
):
457 "[-r <rev>] <path>..."
458 rev
= flags
.get('-r', 'HEAD')
459 paths
= ' '.join(paths
)
460 os
.system("git log --name-status '%s' -- %s" % (rev
, paths
))
462 @short_help("show staged, unstaged, or all uncommitted changes")
464 Show staged, unstaged, or all uncommitted changes. By default, all
465 changes are shown. The '-u' flag causes only unstaged changes to be
466 shown. The '-d' flag causes only staged changes to be shown.
469 def cmd_diff(self
, **flags
):
471 if '-u' in flags
and '-d' in flags
:
472 raise YapError("Conflicting flags: -u and -d")
474 pager
= self
._get
_pager
_cmd
()
477 os
.system("git diff-files -p | %s" % pager
)
479 os
.system("git diff-index --cached -p HEAD | %s" % pager
)
481 os
.system("git diff-index -p HEAD | %s" % pager
)
483 @short_help("list, create, or delete branches")
485 If no arguments are specified, a list of local branches is given. The
486 current branch is indicated by a "*" next to the name. If an argument
487 is given, it is taken as the name of a new branch to create. The branch
488 will start pointing at the current HEAD. See 'point' for details on
489 changing the revision of the new branch. Note that this command does
490 not switch the current working branch. See 'switch' for details on
491 changing the current working branch.
493 The '-d' flag can be used to delete local branches. If the delete
494 operation would remove the last branch reference to a given line of
495 history (colloquially referred to as "dangling commits"), yap will
496 report an error and abort. The '-f' flag can be used to force the delete
499 @takes_options("fd:")
500 def cmd_branch(self
, branch
=None, **flags
):
501 "[ [-f] -d <branch> | <branch> ]"
502 force
= '-f' in flags
504 self
._delete
_branch
(flags
['-d'], force
)
508 if branch
is not None:
509 ref
= get_output("git rev-parse --verify HEAD")
511 raise YapError("No branch point yet. Make a commit")
512 run_safely("git update-ref 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
514 current
= get_output("git symbolic-ref HEAD")[0]
515 branches
= get_output("git for-each-ref --format='%(refname)' 'refs/heads/*'")
521 b
= b
.replace('refs/heads/', '')
524 @short_help("change the current working branch")
526 The argument is the name of the branch to make the current working
527 branch. This command will fail if there are uncommitted changes to any
528 files. Otherwise, the contents of the files in the working directory
529 are updated to reflect their state in the new branch. Additionally, any
530 future commits are added to the new branch instead of the previous line
533 def cmd_switch(self
, branch
):
535 ref
= get_output("git rev-parse --verify 'refs/heads/%s'" % branch
)
537 raise YapError("No such branch: %s" % branch
)
539 # XXX: support merging like git-checkout
540 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
541 raise YapError("You have uncommitted changes. Commit them first")
543 run_safely("git symbolic-ref HEAD refs/heads/'%s'" % branch
)
544 run_safely("git read-tree -u -m HEAD")
545 run_safely("git checkout-index -u -f -a")
548 @short_help("move the current branch to a different revision")
550 The argument is the hash of the commit to which the current branch
551 should point, or alternately a branch or tag (a.k.a, "committish"). If
552 moving the branch would create "dangling commits" (see 'branch'), yap
553 will report an error and abort. The '-f' flag can be used to force the
554 operation in spite of this.
557 def cmd_point(self
, where
, **flags
):
559 head
= get_output("git rev-parse --verify HEAD")
561 raise YapError("No commit yet; nowhere to point")
563 ref
= get_output("git rev-parse --verify '%s'" % where
)
565 raise YapError("Not a valid ref: %s" % where
)
567 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
568 raise YapError("You have uncommitted changes. Commit them first")
570 type = get_output("git cat-file -t '%s'" % ref
[0])
571 if type and type[0] == "tag":
572 tag
= get_output("git cat-file tag '%s'" % ref
[0])
573 ref
[0] = tag
[0].split(' ')[1]
575 run_safely("git update-ref HEAD '%s'" % ref
[0])
577 if '-f' not in flags
:
578 name
= get_output("git name-rev --name-only '%s'" % head
[0])[0]
579 if name
== "undefined":
580 os
.system("git update-ref HEAD '%s'" % head
[0])
581 raise YapError("Pointing there will lose commits. Use -f to force")
583 run_safely("git read-tree -u -m HEAD")
584 run_safely("git checkout-index -u -f -a")
586 @short_help("alter history by dropping or amending commits")
588 This command operates in two distinct modes, "amend" and "drop" mode.
589 In drop mode, the given commit is removed from the history of the
590 current branch, as though that commit never happened. By default the
593 In amend mode, the uncommitted changes present are merged into a
594 previous commit. This is useful for correcting typos or adding missed
595 files into past commits. By default the commit used is HEAD.
597 While rewriting history it is possible that conflicts will arise. If
598 this happens, the rewrite will pause and you will be prompted to resolve
599 the conflicts and stage them. Once that is done, you will run "yap
600 history continue." If instead you want the conflicting commit removed
601 from history (perhaps your changes supercede that commit) you can run
602 "yap history skip". Once the rewrite completes, your branch will be on
603 the same commit as when the rewrite started.
605 def cmd_history(self
, subcmd
, *args
):
606 "amend | drop <commit>"
608 if subcmd
not in ("amend", "drop", "continue", "skip"):
612 When you have resolved the conflicts run \"yap history continue\".
613 To skip the problematic patch, run \"yap history skip\"."""
615 if subcmd
== "continue":
616 os
.system("git am -3 -r --resolvemsg='%s'" % resolvemsg
)
619 os
.system("git reset --hard")
620 os
.system("git am -3 --skip --resolvemsg='%s'" % resolvemsg
)
623 if subcmd
== "amend":
624 flags
, args
= getopt
.getopt(args
, "ad")
634 if run_command("git rev-parse --verify '%s'" % commit
):
635 raise YapError("Not a valid commit: %s" % commit
)
637 self
._check
_rebasing
()
639 if subcmd
== "amend":
640 self
._check
_commit
(**flags
)
641 if self
._get
_unstaged
_files
():
642 # XXX: handle unstaged changes better
643 raise YapError("Commit away changes that you aren't amending")
646 stash
= get_output("git stash create")
647 run_command("git reset --hard")
648 if subcmd
== "amend" and not stash
:
649 raise YapError("Failed to stash; no changes?")
652 fd
, tmpfile
= tempfile
.mkstemp("yap")
654 os
.system("git format-patch -k --stdout '%s' > %s" % (commit
, tmpfile
))
655 if subcmd
== "amend":
656 self
.cmd_point(commit
, **{'-f': True})
658 if subcmd
== "amend":
659 rc
= os
.system("git stash apply --index %s" % stash
[0])
661 raise YapError("Failed to apply stash")
664 if subcmd
== "amend":
668 self
.cmd_point("%s^" % commit
, **{'-f': True})
670 stat
= os
.stat(tmpfile
)
673 rc
= os
.system("git am -3 --resolvemsg=\'%s\' %s" % (resolvemsg
, tmpfile
))
675 raise YapError("Failed to apply changes")
678 run_command("git stash apply --index %s" % stash
[0])
683 @short_help("show the changes introduced by a given commit")
685 By default, the changes in the last commit are shown. To override this,
686 specify a hash, branch, or tag (committish). The hash of the commit,
687 the commit's author, log message, and a diff of the changes are shown.
689 def cmd_show(self
, commit
="HEAD"):
691 os
.system("git show '%s'" % commit
)
693 @short_help("apply the changes in a given commit to the current branch")
695 The argument is the hash, branch, or tag (committish) of the commit to
696 be applied. In general, it only makes sense to apply commits that
697 happened on another branch. The '-r' flag can be used to have the
698 changes in the given commit reversed from the current branch. In
699 general, this only makes sense for commits that happened on the current
703 def cmd_cherry_pick(self
, commit
, **flags
):
706 os
.system("git revert '%s'" % commit
)
708 os
.system("git cherry-pick '%s'" % commit
)
710 @short_help("list, add, or delete configured remote repositories")
712 When invoked with no arguments, this command will show the list of
713 currently configured remote repositories, giving both the name and URL
714 of each. To add a new repository, give the desired name as the first
715 argument and the URL as the second. The '-d' flag can be used to remove
716 a previously added repository.
719 def cmd_repo(self
, name
=None, url
=None, **flags
):
720 "[<name> <url> | -d <name>]"
721 if name
is not None and url
is None:
725 if flags
['-d'] not in [ x
[0] for x
in self
._list
_remotes
() ]:
726 raise YapError("No such repository: %s" % flags
['-d'])
727 os
.system("git config --unset remote.%s.url" % flags
['-d'])
728 os
.system("git config --unset remote.%s.fetch" % flags
['-d'])
731 if flags
['-d'] in [ x
[0] for x
in self
._list
_remotes
() ]:
732 raise YapError("Repository '%s' already exists" % flags
['-d'])
733 os
.system("git config remote.%s.url %s" % (name
, url
))
734 os
.system("git config remote.%s.fetch +refs/heads/*:refs/remotes/%s/*" % (name
, url
))
736 for remote
, url
in self
._list
_remotes
():
737 print "%-20s %s" % (remote
, url
)
740 def cmd_push(self
, repo
, **flags
):
743 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
744 raise YapError("No such repository: %s" % repo
)
746 current
= get_output("git symbolic-ref HEAD")
748 raise YapError("Not on a branch!")
750 current
= current
[0].replace('refs/heads/', '')
751 remote
= get_output("git config branch.%s.remote" % current
)
752 if remote
and remote
[0] == repo
:
753 merge
= get_output("git config branch.%s.merge" % current
)
757 if '-c' not in flags
and '-d' not in flags
:
758 if run_command("git rev-parse --verify refs/remotes/%s/%s"
759 % (repo
, ref
.replace('refs/heads/', ''))):
760 raise YapError("No matching branch on that repo. Use -c to create a new branch there.")
765 lhs
= "refs/heads/%s" % current
766 rc
= os
.system("git push %s %s:%s" % (repo
, lhs
, ref
))
768 raise YapError("Push failed.")
770 def cmd_fetch(self
, repo
):
771 # XXX allow defaulting of repo? yap.default
772 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
773 raise YapError("No such repository: %s" % repo
)
774 os
.system("git fetch %s" % repo
)
776 def cmd_help(self
, cmd
=None):
779 attr
= self
.__getattribute
__("cmd_"+cmd
.replace('-', '_'))
780 except AttributeError:
781 raise YapError("No such command: %s" % cmd
)
783 help = attr
.long_help
784 except AttributeError:
785 raise YapError("Sorry, no help for '%s'. Ask Steven." % cmd
)
787 print >>sys
.stderr
, "The '%s' command" % cmd
788 print >>sys
.stderr
, "\tyap %s %s" % (cmd
, attr
.__doc
__)
789 print >>sys
.stderr
, "%s" % help
792 print >> sys
.stderr
, "Yet Another (Git) Porcelein"
795 for name
in dir(self
):
796 if not name
.startswith('cmd_'):
798 attr
= self
.__getattribute
__(name
)
799 if not callable(attr
):
802 short_msg
= attr
.short_help
803 except AttributeError:
806 name
= name
.replace('cmd_', '')
807 name
= name
.replace('_', '-')
808 print >> sys
.stderr
, "%-16s%s" % (name
, short_msg
)
810 print >> sys
.stderr
, "(*) Indicates that the command is not readily reversible"
813 print >> sys
.stderr
, "usage: %s <command>" % os
.path
.basename(sys
.argv
[0])
814 print >> sys
.stderr
, " valid commands: help init clone add rm stage unstage status revert commit uncommit log show diff branch switch point cherry-pick repo history version"
816 def main(self
, args
):
824 debug
= os
.getenv('YAP_DEBUG')
827 command
= command
.replace('-', '_')
830 for p
in self
.plugins
:
832 meth
= p
.__getattribute
__("cmd_"+command
)
833 except AttributeError:
837 default_meth
= self
.__getattribute
__("cmd_"+command
)
838 except AttributeError:
847 if "options" in meth
.__dict
__:
848 options
= meth
.options
849 if default_meth
and "options" in default_meth
.__dict
__:
850 options
+= default_meth
.options
851 flags
, args
= getopt
.getopt(args
, options
)
857 for p
in self
.plugins
:
859 meth
= p
.__getattribute
__("pre_"+command
)
860 except AttributeError:
867 for p
in self
.plugins
:
869 meth
= p
.__getattribute
__("post_"+command
)
870 except AttributeError:
874 except (TypeError, getopt
.GetoptError
):
877 print "%s %s %s" % (sys
.argv
[0], command
, meth
.__doc
__)
879 print >> sys
.stderr
, e
881 except AttributeError: