PEP8: fix E302: expected 2 blank lines, found 1
[samba.git] / python / samba / netcmd / __init__.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009-2012
3 # Copyright (C) Theresa Halloran <theresahalloran@gmail.com> 2011
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import optparse, samba
20 from samba import getopt as options
21 from samba import colour
22 from ldb import LdbError
23 import sys, traceback
24 import textwrap
25
26
27 class Option(optparse.Option):
28     pass
29
30 # This help formatter does text wrapping and preserves newlines
31
32
33 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
34     def format_description(self, description=""):
35         desc_width = self.width - self.current_indent
36         indent = " " * self.current_indent
37         paragraphs = description.split('\n')
38         wrapped_paragraphs = [
39             textwrap.fill(p,
40                           desc_width,
41                           initial_indent=indent,
42                           subsequent_indent=indent)
43             for p in paragraphs]
44         result = "\n".join(wrapped_paragraphs) + "\n"
45         return result
46
47     def format_epilog(self, epilog):
48         if epilog:
49             return "\n" + epilog + "\n"
50         else:
51             return ""
52
53
54 class Command(object):
55     """A samba-tool command."""
56
57     def _get_short_description(self):
58         return self.__doc__.splitlines()[0].rstrip("\n")
59
60     short_description = property(_get_short_description)
61
62     def _get_full_description(self):
63         lines = self.__doc__.split("\n")
64         return lines[0] + "\n" + textwrap.dedent("\n".join(lines[1:]))
65
66     full_description = property(_get_full_description)
67
68     def _get_name(self):
69         name = self.__class__.__name__
70         if name.startswith("cmd_"):
71             return name[4:]
72         return name
73
74     name = property(_get_name)
75
76     # synopsis must be defined in all subclasses in order to provide the
77     # command usage
78     synopsis = None
79     takes_args = []
80     takes_options = []
81     takes_optiongroups = {}
82
83     hidden = False
84
85     raw_argv = None
86     raw_args = None
87     raw_kwargs = None
88
89     def __init__(self, outf=sys.stdout, errf=sys.stderr):
90         self.outf = outf
91         self.errf = errf
92
93     def usage(self, prog, *args):
94         parser, _ = self._create_parser(prog)
95         parser.print_usage()
96
97     def show_command_error(self, e):
98         '''display a command error'''
99         if isinstance(e, CommandError):
100             (etype, evalue, etraceback) = e.exception_info
101             inner_exception = e.inner_exception
102             message = e.message
103             force_traceback = False
104         else:
105             (etype, evalue, etraceback) = sys.exc_info()
106             inner_exception = e
107             message = "uncaught exception"
108             force_traceback = True
109
110         if isinstance(inner_exception, LdbError):
111             (ldb_ecode, ldb_emsg) = inner_exception.args
112             self.errf.write("ERROR(ldb): %s - %s\n" % (message, ldb_emsg))
113         elif isinstance(inner_exception, AssertionError):
114             self.errf.write("ERROR(assert): %s\n" % message)
115             force_traceback = True
116         elif isinstance(inner_exception, RuntimeError):
117             self.errf.write("ERROR(runtime): %s - %s\n" % (message, evalue))
118         elif type(inner_exception) is Exception:
119             self.errf.write("ERROR(exception): %s - %s\n" % (message, evalue))
120             force_traceback = True
121         elif inner_exception is None:
122             self.errf.write("ERROR: %s\n" % (message))
123         else:
124             self.errf.write("ERROR(%s): %s - %s\n" % (str(etype), message, evalue))
125             force_traceback = True
126
127         if force_traceback or samba.get_debug_level() >= 3:
128             traceback.print_tb(etraceback, file=self.errf)
129
130     def _create_parser(self, prog, epilog=None):
131         parser = optparse.OptionParser(
132             usage=self.synopsis,
133             description=self.full_description,
134             formatter=PlainHelpFormatter(),
135             prog=prog, epilog=epilog)
136         parser.add_options(self.takes_options)
137         optiongroups = {}
138         for name, optiongroup in self.takes_optiongroups.items():
139             optiongroups[name] = optiongroup(parser)
140             parser.add_option_group(optiongroups[name])
141         return parser, optiongroups
142
143     def message(self, text):
144         self.outf.write(text + "\n")
145
146     def _run(self, *argv):
147         parser, optiongroups = self._create_parser(argv[0])
148         opts, args = parser.parse_args(list(argv))
149         # Filter out options from option groups
150         args = args[1:]
151         kwargs = dict(opts.__dict__)
152         for option_group in parser.option_groups:
153             for option in option_group.option_list:
154                 if option.dest is not None:
155                     del kwargs[option.dest]
156         kwargs.update(optiongroups)
157
158         # Check for a min a max number of allowed arguments, whenever possible
159         # The suffix "?" means zero or one occurence
160         # The suffix "+" means at least one occurence
161         # The suffix "*" means zero or more occurences
162         min_args = 0
163         max_args = 0
164         undetermined_max_args = False
165         for i, arg in enumerate(self.takes_args):
166             if arg[-1] != "?" and arg[-1] != "*":
167                 min_args += 1
168             if arg[-1] == "+" or arg[-1] == "*":
169                 undetermined_max_args = True
170             else:
171                 max_args += 1
172         if (len(args) < min_args) or (not undetermined_max_args and len(args) > max_args):
173             parser.print_usage()
174             return -1
175
176         self.raw_argv = list(argv)
177         self.raw_args = args
178         self.raw_kwargs = kwargs
179
180         try:
181             return self.run(*args, **kwargs)
182         except Exception as e:
183             self.show_command_error(e)
184             return -1
185
186     def run(self):
187         """Run the command. This should be overridden by all subclasses."""
188         raise NotImplementedError(self.run)
189
190     def get_logger(self, name="netcmd"):
191         """Get a logger object."""
192         import logging
193         logger = logging.getLogger(name)
194         logger.addHandler(logging.StreamHandler(self.errf))
195         return logger
196
197     def apply_colour_choice(self, requested):
198         """Heuristics to work out whether the user wants colour output, from a
199         --color=yes|no|auto option. This alters the ANSI 16 bit colour
200         "constants" in the colour module to be either real colours or empty
201         strings.
202         """
203         requested = requested.lower()
204         if requested == 'no':
205             colour.switch_colour_off()
206
207         elif requested == 'yes':
208             colour.switch_colour_on()
209
210         elif requested == 'auto':
211             if (hasattr(self.outf, 'isatty') and self.outf.isatty()):
212                 colour.switch_colour_on()
213             else:
214                 colour.switch_colour_off()
215
216         else:
217             raise CommandError("Unknown --color option: %s "
218                                "please choose from yes, no, auto")
219
220
221 class SuperCommand(Command):
222     """A samba-tool command with subcommands."""
223
224     synopsis = "%prog <subcommand>"
225
226     subcommands = {}
227
228     def _run(self, myname, subcommand=None, *args):
229         if subcommand in self.subcommands:
230             return self.subcommands[subcommand]._run(
231                 "%s %s" % (myname, subcommand), *args)
232
233         if subcommand == 'help':
234             # pass the request down
235             if len(args) > 0:
236                 sub = self.subcommands.get(args[0])
237                 if isinstance(sub, SuperCommand):
238                     return sub._run("%s %s" % (myname, args[0]), 'help',
239                                     *args[1:])
240                 elif sub is not None:
241                     return sub._run("%s %s" % (myname, args[0]), '--help',
242                                     *args[1:])
243
244             subcommand = '--help'
245
246         epilog = "\nAvailable subcommands:\n"
247         subcmds = self.subcommands.keys()
248         subcmds.sort()
249         max_length = max([len(c) for c in subcmds])
250         for cmd_name in subcmds:
251             cmd = self.subcommands[cmd_name]
252             if not cmd.hidden:
253                 epilog += "  %*s  - %s\n" % (
254                     -max_length, cmd_name, cmd.short_description)
255         epilog += "For more help on a specific subcommand, please type: %s <subcommand> (-h|--help)\n" % myname
256
257         parser, optiongroups = self._create_parser(myname, epilog=epilog)
258         args_list = list(args)
259         if subcommand:
260             args_list.insert(0, subcommand)
261         opts, args = parser.parse_args(args_list)
262
263         parser.print_help()
264         return -1
265
266
267 class CommandError(Exception):
268     """An exception class for samba-tool Command errors."""
269
270     def __init__(self, message, inner_exception=None):
271         self.message = message
272         self.inner_exception = inner_exception
273         self.exception_info = sys.exc_info()
274
275     def __repr__(self):
276         return "CommandError(%s)" % self.message