This file is indexed.

/usr/lib/python3/dist-packages/csb/apps/__init__.py is in python3-csb 1.2.3+dfsg-3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
"""
Root package for all executable CSB client programs.

Introduction
============

There are roughly three types of CSB apps:
    
    1. protocols: client applications, which make use of the core library
       to perform some action    
    2. wrappers: these provide python bindings for external programs
    3. mixtures of (1) and (2).

The main design goal of this framework is to provide a way for writing
executable code with minimal effort, without the hassle of repeating yourself
over and over again. Creating a professional-grade CLI, validating and
consuming the command line arguments is therefore really straightforward.
On the other hand, one frequently feels the need to reuse some apps or their
components in other apps. For such reasons, a CSB L{Application} is just a
regular, importable python object, which never communicates directly with the
command line interface or calls sys.exit(). The app's associated L{AppRunner}
will take care of those things.      

Getting Started
===============

Follow these simple steps to write a new CSB app:

    1. Create the app module in the C{csb.apps} package.
    
    2. Create a main class and derive it from L{csb.apps.Application}. You need
    to implement the L{csb.apps.Application.main()} abstract method - this is
    the app's entry point. You have the L{csb.apps.Application.args} object at
    your disposal.
    
    3. Create an AppRunner class, derived from csb.apps.AppRunner. You need to
    implement the following methods and properties:
    
        - property L{csb.apps.AppRunner.target} -- just return YourApp's class
        - method L{csb.apps.AppRunner.command_line()} -- make an instance of
        L{csb.apps.ArgHandler}, define your command line parameters on that
        instance and return it
        - optionally, override L{csb.apps.AppRunner.initapp(args)} if you need
        to customize the instantiation of the main app class, or to perform
        additional checks on the parsed application C{args} and eventually call
        C{YourApp.exit()}. Return an instance of your app at the end 
    
    4. Make it executable::
        if __name__ == '__main__':
            MyAppRunner().run()
    
See L{csb.apps.helloworld} for a sample implementation.    
"""

import os
import re
import sys
import argparse
import traceback

from abc import ABCMeta, abstractmethod, abstractproperty


class ExitCodes(object):
    """
    Exit code constants.
    """
    CLEAN = 0
    USAGE_ERROR = 1
    CRASH = 99
    
class AppExit(Exception):
    """
    Used to signal an immediate application exit condition (e.g. a fatal error),
    that propagates down to the client, instead of forcing the interpreter to
    close via C{sys.exit()}.
    
    @param message: exit message
    @type message: str
    @param code: exit code (see L{ExitCodes} for common constants)
    @type code: int
    @param usage: ask the app runner to print also the app's usage line
    @type usage: bool
    """
    
    def __init__(self, message='', code=0, usage=False):
        
        self.message = message
        self.code = code
        self.usage = usage
        
        super(AppExit, self).__init__(message, code, usage)

class Application(object):
    """
    Base CSB application class.
    
    @param args: an object containing the application arguments
    @type args: argparse.Namespace
    """
    __metaclass__ = ABCMeta
    
    def __init__(self, args, log=sys.stdout):
        
        self.__args = None
        self._log = log
                
        self.args = args
        
    @property
    def args(self):
        """
        The object containing application's arguments, as returned by the
        command line parser.
        """
        return self.__args
    @args.setter
    def args(self, args):
        self.__args = args
    
    @abstractmethod
    def main(self):
        """
        The main application hook.
        """
        pass
    
    def log(self, message, ending='\n'):
        """
        Write C{message} to the logging stream and flush it.
        
        @param message: message
        @type message: str        
        """
        
        self._log.write(message)
        self._log.write(ending)
        self._log.flush()
        
    @staticmethod
    def exit(message, code=0, usage=False):
        """
        Notify the app runner about an application exit.

        @param message: exit message
        @type message: str
        @param code: exit code (see L{ExitCodes} for common constants)
        @type code: int
        @param usage: advise the client to show the usage line
        @type usage: bool        
        
        @note: you re not supposed to use C{sys.exit()} for the same purpose.
               It is L{AppRunner}'s responsibility to handle the real system
               exit, if the application has been started as an executable.
               Think about your app being executed by some Python client as a
               regular Python class, imported from a module -- in that case you
               only want to ask the client to terminate the app, not to kill
               the whole interpreter.   
        """
        raise AppExit(message, code, usage)        
            
class AppRunner(object):
    """
    A base abstract class for all application runners. Concrete sub-classes
    must define their corresponding L{Application} using the L{self.target}
    property and must customize the L{Application}'s command line parser using
    L{self.command_line()}.
    
    @param argv: the list of command line arguments passed to the program. By
                 default this is C{sys.argv}.
    @type argv: tuple of str 
    """
    __metaclass__ = ABCMeta
        
    def __init__(self, argv=sys.argv):
        
        self._module = argv[0]
        self._program = os.path.basename(self.module)
        self._args = argv[1:]
        
    @property
    def module(self):
        return self._module
    
    @property
    def program(self):
        return self._program
    
    @property
    def args(self):
        return self._args
    
    @abstractproperty
    def target(self):
        """
        Reference to the concrete L{Application} class to run. This is
        an abstract property that couples the current C{AppRunner} to its
        corresponding L{Application}.

        @rtype: type (class reference)
        """
        return Application 
    
    @abstractmethod
    def command_line(self):
        """
        Command line factory: build a command line parser suitable for the
        application.
        This is a hook method that each concrete AppRunner must implement.
        
        @return: a command line parser object which knows how to handle
        C{sys.argv} in the context of the concrete application. See the
        documentation of L{ArgHandler} for more info on how to define command
        line arguments.
        
        @rtype: L{ArgHandler}
        """
        # null implementation (no cmd arguments):
        return ArgHandler(self.program)
    
    def initapp(self, args):
        """
        Hook method that controls the instantiation of the main app class.
        If the application has a custom constructor, you can adjust the
        app initialization by overriding this method.
        
        @param args: an object containing the application arguments
        @type args: argparse.Namespace
        
        @return: the application instance
        @rtype: L{Application}
        """
        app = self.target
        return app(args)

    def run(self):
        """
        Get the L{self.command_line()} and run L{self.target}. Ensure clean
        system exit. 
        """
        try:
            app = self.target
            cmd = self.command_line()
                    
            try:           
                assert issubclass(app, Application)
                assert isinstance(cmd, ArgHandler)
                            
                args = cmd.parse(self.args)
                app.USAGE = cmd.usage
                app.HELP = cmd.help
    
                self.initapp(args).main()
                
            except AppExit as ae:
                if ae.usage:
                    AppRunner.exit(ae.message, code=ae.code, usage=cmd.usage)
                else:
                    AppRunner.exit(ae.message, code=ae.code)
    
            except SystemExit as se:                            # this should never happen, but just in case 
                AppRunner.exit(se.message, code=se.code)
                        
        except Exception:
            message = '{0} has crashed. Details: \n{1}'.format(self.program, traceback.format_exc())
            AppRunner.exit(message, code=ExitCodes.CRASH)
        
        AppRunner.exit(code=ExitCodes.CLEAN)            
            
    @staticmethod
    def exit(message='', code=0, usage='', ending='\n'):
        """
        Perform system exit. If the exit C{code} is 0, print all messages to
        STDOUT, else write to STDERR.
        
        @param message: message to print
        @type message: str
        @param code: application exit code
        @type code: int   
        """
        
        ending = str(ending or '')
        message = str(message or '')
        stream = sys.stdout
        
        if code > 0:
            message = 'E#{0} {1}'.format(code, message)
            stream = sys.stderr
        
        if usage:
            stream.write(usage.rstrip(ending))            
            stream.write(ending)
        if message:
            stream.write(message)            
            stream.write(ending)
        
        sys.exit(code)

class ArgHandler(object):
    """
    Command line argument handler.
    
    @param program: (file)name of the program, usually sys.argv[0]
    @type program: str
    @param description: long description of the application, shown in help
                        pages. The usage line and the parameter lists are
                        generated automatically, so no need to put them here.
    @type description: str
    
    @note: a help argument (-h) is provided automatically. 
    """
    
    SHORT_PREFIX = '-'
    LONG_PREFIX = '--'
    
    class Type(object):
        
        POSITIONAL = 1
        NAMED = 2
    
    def __init__(self, program, description=''):
        
        self._argformat = re.compile('^[a-z][a-z0-9_-]*$', re.IGNORECASE)
        self._optformat = re.compile('^[a-z0-9]$', re.IGNORECASE)
        
        self._program = program
        self._description = description
        
        self._parser = argparse.ArgumentParser(prog=program, description=description)
        
    def _add(self, kind, name, shortname, help="", *a, **k):
        
        args = []
        kargs = dict(k)
                    
        if shortname is not None:
            if not re.match(self._optformat, shortname):
                raise ValueError('Invalid short option name: {0}.'.format(shortname))

            if kind == ArgHandler.Type.POSITIONAL:
                args.append(shortname)
            else:                     
                args.append(ArgHandler.SHORT_PREFIX + shortname)

        if name is not None or kind == ArgHandler.Type.POSITIONAL:
            if not re.match(self._argformat, name):
                raise ValueError('Malformed argument name: {0}.'.format(name))
            
            if kind == ArgHandler.Type.POSITIONAL:
                args.append(name)
            else:
                args.append(ArgHandler.LONG_PREFIX + name)

        assert len(args) in (1, 2)   
        args.extend(a)
        kargs["help"] = help.replace("%", "%%")     # workaround for a bug in argparse           
        
        self.parser.add_argument(*args, **kargs)
        
    def _format_help(self, help, default):

        if not help:
            help = ''
        if default is not None:
            help = '{0} (default={1})'.format(help, default)
            
        return help
        
    def add_positional_argument(self, name, type, help, choices=None):
        """
        Define a mandatory positional argument (an argument without a dash).
        
        @param name: name of the argument (used in help only)
        @type name: str
        @param type: argument data type
        @type type: type (type factory callable)
        @param help: help text
        @type help: str
        @param choices: list of allowed argument values
        @type choices: tuple
        """
        self._add(ArgHandler.Type.POSITIONAL, name, None,
                  type=type, help=help, choices=choices)

    def add_array_argument(self, name, type, help, choices=None):
        """
        Same as L{self.add_positional_argument()}, but allow unlimited number
        of values to be specified on the command line.
        
        @param name: name of the argument (used in help only)
        @type name: str
        @param type: argument data type
        @type type: type (type factory callable)
        @param help: help text
        @type help: str
        @param choices: list of allowed argument values
        @type choices: tuple
        """
        self._add(ArgHandler.Type.POSITIONAL, name, None,
                  type=type, help=help, choices=choices, nargs=argparse.ONE_OR_MORE)        

    def add_boolean_option(self, name, shortname, help, default=False):
        """
        Define an optional switch (a dashed argument with no value).
        
        @param name: long name of the option (or None)
        @type name: str, None
        @param shortname: short (single character) name of the option (or None)
        @type shortname:str, None
        @param help: help text
        @type help: str
        @param default: default value, assigned when the option is omitted. 
                        If the option is specified on the command line, the
                        inverse value is assigned  
        @type default: bool       
        """
        if not default:
            default = False
        help = self._format_help(help, default)
        
        if default:
            action = 'store_false'
        else:
            action = 'store_true'
                     
        self._add(ArgHandler.Type.NAMED, name, shortname,
                  help=help, action=action, default=bool(default))
        
    def add_scalar_option(self, name, shortname, type, help, default=None, choices=None, required=False):
        """
        Define a scalar option (a dashed argument that accepts a single value).
        
        @param name: long name of the option (or None)
        @type name: str, None
        @param shortname: short (single character) name of the option (or None)
        @type shortname: str, None
        @param type: argument data type
        @type type: type (type factory callable)        
        @param help: help text
        @type help: str
        @param default: default value, assigned when the option is omitted
        @param choices: list of allowed argument values
        @type choices: tuple
        @param required: make this option a named mandatory argument
        @type required: bool      
        """
        help = self._format_help(help, default)          
         
        self._add(ArgHandler.Type.NAMED, name, shortname,
                  type=type, help=help, default=default, choices=choices, required=required)        

    def add_array_option(self, name, shortname, type, help, default=None, choices=None, required=False):
        """
        Define an array option (a dashed argument that may receive one
        or multiple values on the command line, separated with spaces).

        @param name: long name of the option (or None)
        @type name: str, None
        @param shortname: short (single character) name of the option (or None)
        @type shortname: str, None
        @param type: argument data type
        @type type: type (type factory callable)        
        @param help: help text
        @type help: str
        @param choices: list of allowed argument values
        @type choices: tuple
        @param required: make this option a named mandatory argument
        @type required: bool                   
        """
        help = self._format_help(help, default)      
        
        self._add(ArgHandler.Type.NAMED, name, shortname,
                  nargs=argparse.ZERO_OR_MORE, type=type, help=help, default=default,
                  choices=choices, required=required)
        
    def parse(self, args):
        """
        Parse the command line arguments.
        
        @param args: the list of user-provided command line arguments --
                     normally sys.argv[1:]
        @type args: tuple of str        
        
        @return: an object initialized with the parsed arguments
        @rtype: argparse.Namespace
        """
        try:
            return self.parser.parse_args(args)
        except SystemExit as se:
            if se.code > 0:
                raise AppExit('Bad command line', ExitCodes.USAGE_ERROR)
            else:
                raise AppExit(code=ExitCodes.CLEAN)                
    
    @property
    def parser(self):
        return self._parser
    
    @property
    def usage(self):
        return self.parser.format_usage()

    @property
    def help(self):
        return self.parser.format_help()