Source code for plainbox.impl.secure.origin

# This file is part of Checkbox.
#
# Copyright 2012-2014 Canonical Ltd.
# Written by:
#   Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.

#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.

"""
:mod:`plainbox.impl.secure.origin` -- origin objects
====================================================
"""

import functools
import inspect
import os

from plainbox.abc import ITextSource
from plainbox.i18n import gettext as _
from plainbox.impl.symbol import SymbolDef


[docs]class OriginMode(SymbolDef): """ A symbol definition (which will become an enumeration in the near future) that describes all the possible "modes" an :class:`Origin` can operate in. """ # NOTE: this should be an enumeration whole_file = 'whole-file' single_line = 'single-line' line_range = 'line-range'
[docs]@functools.total_ordering class Origin: """ Simple class for tracking where something came from This class supports "pinpointing" something in a block of text. The block is described by the source attribute. The actual range is described by line_start (inclusive) and line_end (exclusive). :ivar source: Something that describes where the text came frome. Technically it should implement the :class:`~plainbox.abc.ITextSource` interface. :ivar line_start: The number of the line where the record begins. This can be None when the intent is to cover the whole file. This can also be equal to line_end (when not None) if the intent is to show a single line. :ivar line_end: The number of the line where the record ends """ __slots__ = ['source', 'line_start', 'line_end'] def __init__(self, source, line_start=None, line_end=None): self.source = source self.line_start = line_start self.line_end = line_end
[docs] def mode(self): """ Compute the "mode" of this origin instance. :returns: :attr:`OriginMode.whole_file`, :attr:`OriginMode.single_line` or :attr:`OriginMode.line_range`. The mode tells if this instance is describing the whole file, a range of lines or just a single line. It is mostly used internally by the implementation. """ if self.line_start is None and self.line_end is None: return OriginMode.whole_file elif self.line_start == self.line_end: return OriginMode.single_line else: return OriginMode.line_range
def __repr__(self): return "<{} source:{!r} line_start:{} line_end:{}>".format( self.__class__.__name__, self.source, self.line_start, self.line_end) def __str__(self): mode = self.mode() if mode is OriginMode.whole_file: return str(self.source) elif mode is OriginMode.single_line: return "{}:{}".format(self.source, self.line_start) elif mode is OriginMode.line_range: return "{}:{}-{}".format( self.source, self.line_start, self.line_end) else: raise NotImplementedError
[docs] def relative_to(self, base_dir): """ Create a Origin with source relative to the specified base directory. :param base_dir: A base directory name :returns: A new Origin with source replaced by the result of calling relative_to(base_dir) on the current source *iff* the current source has that method, self otherwise. This method is useful for obtaining user friendly Origin objects that have short, understandable filenames. """ relative_source = self.source.relative_to(base_dir) if relative_source is not self.source: return Origin(relative_source, self.line_start, self.line_end) else: return self
[docs] def with_offset(self, offset): """ Create a new Origin by adding a offset of a specific number of lines :param offset: Number of lines to add (or substract) :returns: A new Origin object """ mode = self.mode() if mode is OriginMode.whole_file: return self elif mode is OriginMode.single_line or mode is OriginMode.line_range: return Origin(self.source, self.line_start + offset, self.line_end + offset) else: raise NotImplementedError
[docs] def just_line(self): """ Create a new Origin that points to the start line :returns: A new Origin with the end_line equal to start_line. This effectively makes the origin describe a single line. """ return Origin(self.source, self.line_start, self.line_start)
[docs] def just_file(self): """ create a new Origin that points to the whole file :returns: A new Origin with line_end and line_start both set to None. """ return Origin(self.source)
def __eq__(self, other): if isinstance(other, Origin): return ((self.source, self.line_start, self.line_end) == (other.source, other.line_start, other.line_end)) else: return NotImplemented def __gt__(self, other): if isinstance(other, Origin): return ((self.source, self.line_start, self.line_end) > (other.source, other.line_start, other.line_end)) else: return NotImplemented
[docs] @classmethod def get_caller_origin(cls, back=0): """ Create an Origin instance pointing at the call site of this method. """ # Create an Origin instance that pinpoints the place that called # get_caller_origin(). caller_frame, filename, lineno = inspect.stack(0)[2 + back][:3] try: source = PythonFileTextSource(filename) origin = Origin(source, lineno, lineno) finally: # Explicitly delete the frame object, this breaks the # reference cycle and makes this part of the code deterministic # with regards to the CPython garbage collector. # # As recommended by the python documentation: # http://docs.python.org/3/library/inspect.html#the-interpreter-stack del caller_frame return origin
[docs]@functools.total_ordering class UnknownTextSource(ITextSource): """ A :class:`ITextSource` subclass indicating that the source of text is unknown. This instances of this class are constructed by gen_rfc822_records() when no explicit source is provided and the stream has no name. The serve as non-None values to prevent constructing :class:`PythonFileTextSource` with origin computed from :meth:`Origin.get_caller_origin()` """ def __str__(self): return _("???") def __repr__(self): return "{}()".format(self.__class__.__name__) def __eq__(self, other): if isinstance(other, UnknownTextSource): return True else: return False def __gt__(self, other): if isinstance(other, UnknownTextSource): return False else: return NotImplemented
[docs] def relative_to(self, path): return self
[docs]@functools.total_ordering class FileTextSource(ITextSource): """ A :class:`ITextSource` subclass indicating that text came from a file. :ivar filename: name of the file something comes from """ def __init__(self, filename): self.filename = filename def __str__(self): return self.filename def __repr__(self): return "{}({!r})".format( self.__class__.__name__, self.filename) def __eq__(self, other): if isinstance(other, FileTextSource): return self.filename == other.filename else: return False def __gt__(self, other): if isinstance(other, FileTextSource): return self.filename > other.filename else: return NotImplemented
[docs] def relative_to(self, base_dir): """ Compute a FileTextSource with the filename being a relative path from the specified base directory. :param base_dir: A base directory name :returns: A new FileTextSource with filename relative to that base_dir """ return self.__class__(os.path.relpath(self.filename, base_dir))
[docs]class PythonFileTextSource(FileTextSource): """ A :class:`FileTextSource` subclass indicating the file was a python file. It implements no differences but in some context it might be helpful to differentiate on the type of the source field in the origin of a job definition record. :ivar filename: name of the python filename that something comes from """
[docs]@functools.total_ordering class JobOutputTextSource(ITextSource): """ A :class:`ITextSource` subclass indicating that text came from job output. This class is used by :meth:`SessionState._gen_rfc822_records_from_io_log()` to allow such (generated) jobs to be traced back to the job that generated them. :ivar job: :class:`plainbox.impl.job.JobDefinition` instance that generated the text """ def __init__(self, job): self.job = job def __str__(self): return str(self.job.id) def __repr__(self): return "<{} job:{!r}>".format(self.__class__.__name__, self.job) def __eq__(self, other): if isinstance(other, JobOutputTextSource): return self.job == other.job return NotImplemented def __gt__(self, other): if isinstance(other, JobOutputTextSource): return self.job > other.job return NotImplemented
[docs] def relative_to(self, base_path): return self
[docs]@functools.total_ordering class CommandLineTextSource(ITextSource): """ A :class:`ITextSource` describing text that originated arguments to main() :attr arg_name: The optional name of the argument that describes the arg_value :attr arg_value: The argument that was passed on command line (the actual text) """ def __init__(self, arg_name, arg_value): self.arg_value = arg_value self.arg_name = arg_name def __str__(self): if self.arg_name is not None: return _("command line argument {}={!a}").format( self.arg_name, self.arg_value) else: return _("command line argument {!a}").format(self.arg_value) def __repr__(self): return "<{} arg_name:{!r} arg_value:{!r}>".format( self.__class__.__name__, self.arg_name, self.arg_value) def __eq__(self, other): if isinstance(other, CommandLineTextSource): return (self.arg_name == other.arg_name and self.arg_value == other.arg_value) return NotImplemented def __gt__(self, other): if isinstance(other, CommandLineTextSource): if self.arg_name > other.arg_name: return True if self.arg_value > other.arg_value: return True return False return NotImplemented
[docs] def relative_to(self, base_path): return self
comments powered by Disqus