]> scripts.mit.edu Git - wizard.git/blob - wizard/command/__init__.py
Fix absolute path from environment bug and debug-all-the-time bug.
[wizard.git] / wizard / command / __init__.py
1 import logging
2 import os
3 import sys
4 import optparse
5 import errno
6
7 import wizard
8
9 logging_setup = False
10
11 class Error(wizard.Error):
12     """Base error class for all command errors"""
13     pass
14
15 class PermissionsError(Error):
16     def __init__(self, dir):
17         self.dir = dir
18     def __str__(self):
19         return """
20
21 ERROR: You don't have permissions to access this directory.
22 Do you have tokens for AFS with your root instance, and
23 is your root instance on scripts-security-upd?
24
25 You can check by running the commands 'klist' and
26 'blanche scripts-security-upd'.
27 """
28
29 class NoSuchDirectoryError(Error):
30     def __init__(self, dir):
31         self.dir = dir
32     def __str__(self):
33         return """
34
35 ERROR: No such directory... check your typing
36 """
37
38 def boolish(val):
39     """
40     Parse the contents of an environment variable as a boolean.
41     This recognizes more values as ``False`` than :func:`bool` would.
42
43         >>> boolish("0")
44         False
45         >>> boolish("no")
46         False
47         >>> boolish("1")
48         True
49     """
50     try:
51         return bool(int(val))
52     except (ValueError, TypeError):
53         if val == "No" or val == "no" or val == "false" or val == "False":
54             return False
55         return bool(val)
56
57 def chdir(dir):
58     try:
59         os.chdir(dir)
60     except OSError as e:
61         if e.errno == errno.EACCES:
62             raise PermissionsError(dir)
63         elif e.errno == errno.ENOENT:
64             raise NoSuchDirectoryError(dir)
65         else: raise e
66
67 def makeLogger(options, numeric_args):
68     global logging_setup
69     if logging_setup: return logging.getLogger()
70     context = " ".join(numeric_args)
71     logger = logging.getLogger()
72     logger.setLevel(logging.INFO)
73     stderr = logging.StreamHandler(sys.stderr)
74     stderr.setFormatter(logging.Formatter(" " * int(options.indent) + '%(levelname)s: %(message)s'))
75     dateFormat = "%H:%M:%S"
76     if options.context:
77         logformatter = logging.Formatter("%(asctime)s %(levelname)s -- " + context + ": %(message)s", dateFormat)
78     else:
79         logformatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", dateFormat)
80     if not options.quiet: logger.addHandler(stderr)
81     else: logger.addHandler(NullLogHandler()) # prevent default
82     if options.log_file:
83         file = logging.FileHandler(options.log_file)
84         file.setFormatter(logformatter)
85         logger.addHandler(file)
86         if options.log_file_chmod:
87             os.chmod(options.log_file, int(options.log_file_chmod, 8))
88     if options.debug:
89         logger.setLevel(logging.DEBUG)
90     else:
91         stderr.setLevel(logging.WARNING)
92         if options.verbose or hasattr(options, "dry_run"):
93             stderr.setLevel(logging.INFO)
94         if options.log_file:
95             file.setLevel(logging.INFO)
96     logging_setup = True
97     return logger
98
99 def makeBaseArgs(options, **grab):
100     """Takes parsed options, and breaks them back into a command
101     line string that we can pass into a subcommand"""
102     args = []
103     grab["log_file"]= "--log-file"
104     grab["debug"]   = "--debug"
105     grab["verbose"] = "--verbose"
106     grab["indent"]  = "--indent"
107     grab["quiet"]   = "--quiet"
108     #grab["log_db"] = "--log-db"
109     for k,flag in grab.items():
110         value = getattr(options, k)
111         if not value and k != "indent": continue
112         args.append(flag)
113         if type(value) is not bool:
114             if k == "indent":
115                 value += 4
116             args.append(str(value))
117     args.append("--context") # always have context for a subcommand
118     return args
119
120 class NullLogHandler(logging.Handler):
121     """Log handler that doesn't do anything"""
122     def emit(self, record):
123         pass
124
125 class WizardOptionParser(optparse.OptionParser):
126     """Configures some default user-level options"""
127     def __init__(self, *args, **kwargs):
128         kwargs["add_help_option"] = False
129         optparse.OptionParser.__init__(self, *args, **kwargs)
130     def parse_all(self, argv):
131         self.add_option("-h", "--help", action="help", help=optparse.SUPPRESS_HELP)
132         group = optparse.OptionGroup(self, "Common Options")
133         group.add_option("-v", "--verbose", dest="verbose", action="store_true",
134                 default=boolish(os.getenv("WIZARD_VERBOSE")), help="Turns on verbose output.  Environment variable is WIZARD_VERBOSE")
135         group.add_option("--debug", dest="debug", action="store_true",
136                 default=boolish(os.getenv("WIZARD_DEBUG")), help="Turns on debugging output.  Environment variable is WIZARD_DEBUG")
137         group.add_option("-q", "--quiet", dest="quiet", action="store_true",
138                 default=boolish(os.getenv("WIZARD_QUIET")), help="Turns off output to stdout. Environment variable is WIZARD_QUIET")
139         group.add_option("--log-file", dest="log_file", metavar="FILE",
140                 default=None, help="Logs verbose output to file")
141         group.add_option("--log-file-chmod", dest="log_file_chmod", metavar="CHMOD",
142                 default=None, help="Chmod the log file after opening.  Number is octal. You must chmod the log file 666 and place the file in /tmp if subprocesses are running as different users.")
143         group.add_option("--indent", dest="indent", metavar="WIDTH",
144                 default=0, help="Indents stdout, useful for nested calls")
145         group.add_option("--context", dest="context", action="store_true",
146                 default=False, help="Adds context to logs, useful for parallel processing")
147         self.add_option_group(group)
148         options, numeric_args = self.parse_args(argv)
149         makeLogger(options, numeric_args)
150         return options, numeric_args
151
152 class OptionBaton(object):
153     """Command classes may define options that they sub-commands may
154     use.  Since wizard --global-command subcommand is not a supported
155     mode of operation, these options have to be passed down the command
156     chain until a option parser is ready to take it; this baton is
157     what is passed down."""
158     def __init__(self):
159         self.store = {}
160     def add(self, *args, **kwargs):
161         key = kwargs["dest"] # require this to be set
162         self.store[key] = optparse.make_option(*args, **kwargs)
163     def push(self, option_parser, *args):
164         """Hands off parameters to option parser"""
165         for key in args:
166             option_parser.add_option(self.store[key])