diff options
-rw-r--r-- | .SRCINFO | 36 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | PKGBUILD | 62 | ||||
-rw-r--r-- | configparser34.py | 1253 | ||||
-rw-r--r-- | configparser_api_changes.patch | 46 | ||||
-rw-r--r-- | fix-PyGIWarnings.patch | 109 | ||||
-rw-r--r-- | setlocale.patch | 25 | ||||
-rw-r--r-- | version.patch | 9 |
8 files changed, 1482 insertions, 61 deletions
@@ -1,39 +1,29 @@ -# Generated by mksrcinfo v8 -# Thu May 19 17:12:48 UTC 2016 pkgbase = kazam - pkgdesc = A screencasting program with design in mind + pkgdesc = Screencast and screenshot application created with design in mind pkgver = 1.4.5 - pkgrel = 6 + pkgrel = 9 url = https://launchpad.net/kazam arch = any license = GPL - depends = python depends = python-cairo - depends = python-xdg depends = python-dbus - depends = python-distutils-extra depends = python-gobject - depends = gobject-introspection + depends = python-xdg + depends = python-distutils-extra + depends = libkeybinder3 depends = gstreamer depends = gst-plugins-base - depends = gtk3 - depends = pango - depends = gdk-pixbuf2 + depends = gst-libav depends = libwnck3 - depends = ffmpeg - depends = libmatroska - depends = libtheora - depends = gnome-desktop - optdepends = libkeybinder3: hotkeys support - optdepends = libappindicator3: indicator on Unity panel support - conflicts = kazam-bzr - conflicts = kazam-stable-bzr + depends = libcanberra source = https://launchpad.net/kazam/stable/1.4.5/+download/kazam-1.4.5.tar.gz - source = version.patch source = configparser_api_changes.patch - md5sums = 522ac80fef7123875271b30298ed6877 - md5sums = 847ae2478ae5e35f6e1af49aa9fb3fa9 - md5sums = 8e751e821558c989ac02ef687a7b7339 + source = fix-PyGIWarnings.patch + source = setlocale.patch + sha256sums = 8e4868c09c4b0ad55b3c72407a26ef94020dc1b95ac31dfdea112b5377b6cfe3 + sha256sums = 5dd8d693437377b0e9bcd600cc9fd700c855b9b861ed51f3cd061ab0f52d9988 + sha256sums = 1e886d680bcc09bd90b45d294496d948fceccae0813cbea5273dc8b493d4e324 + sha256sums = 9309d29b33ca78b6c3e3e74222346a0a02068a37c7bf7bf8ce0b50fd79706b82 pkgname = kazam diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..5c262cdf3256 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.tar.* +*.pkg.* +*.log @@ -1,31 +1,53 @@ +# Maintainer: Yurii Kolesykov <root@yurikoles.com> + pkgname=kazam pkgver=1.4.5 -pkgrel=6 -pkgdesc="A screencasting program with design in mind" +pkgrel=9 +pkgdesc="Screencast and screenshot application created with design in mind" arch=('any') url="https://launchpad.net/kazam" license=('GPL') -groups=() -depends=('python' 'python-cairo' 'python-xdg' 'python-dbus' 'python-distutils-extra' 'python-gobject' 'gobject-introspection' 'gstreamer' 'gst-plugins-base' 'gtk3' 'pango' 'gdk-pixbuf2' 'libwnck3' 'ffmpeg' 'libmatroska' 'libtheora' 'gnome-desktop') -optdepends=('libkeybinder3: hotkeys support' 'libappindicator3: indicator on Unity panel support') -conflicts=('kazam-bzr' 'kazam-stable-bzr') -source=("https://launchpad.net/${pkgname}/stable/${pkgver}/+download/${pkgname}-${pkgver}.tar.gz" - 'version.patch' - 'configparser_api_changes.patch') -md5sums=('522ac80fef7123875271b30298ed6877' - '847ae2478ae5e35f6e1af49aa9fb3fa9' - '8e751e821558c989ac02ef687a7b7339') +depends=( + 'python-cairo' + 'python-dbus' + 'python-gobject' + 'python-xdg' + 'python-distutils-extra' + 'libkeybinder3' + 'gstreamer' + 'gst-plugins-base' + 'gst-libav' + 'libwnck3' + 'libcanberra' +) + +source=( + "https://launchpad.net/${pkgname}/stable/${pkgver}/+download/${pkgname}-${pkgver}.tar.gz" + configparser_api_changes.patch + fix-PyGIWarnings.patch + setlocale.patch +) +sha256sums=('8e4868c09c4b0ad55b3c72407a26ef94020dc1b95ac31dfdea112b5377b6cfe3' + '5dd8d693437377b0e9bcd600cc9fd700c855b9b861ed51f3cd061ab0f52d9988' + '1e886d680bcc09bd90b45d294496d948fceccae0813cbea5273dc8b493d4e324' + '9309d29b33ca78b6c3e3e74222346a0a02068a37c7bf7bf8ce0b50fd79706b82') + +_srcname=${pkgname}-${pkgver} build() { - cd "${srcdir}/${pkgname}-${pkgver}" - patch -p1 < "${srcdir}/version.patch" - patch -p1 < "${srcdir}/configparser_api_changes.patch" + cd "${_srcname}" + + local src + for src in "${source[@]}"; do + src="${src%%::*}" + src="${src##*/}" + [[ $src = *.patch ]] || continue + echo "Applying patch $src..." + patch -Np1 < "../$src" + done } package() { - cd "${srcdir}/${pkgname}-${pkgver}" - python3 setup.py install --root ${pkgdir} + cd "${_srcname}" + python3 setup.py install --root ${pkgdir} } - - - diff --git a/configparser34.py b/configparser34.py new file mode 100644 index 000000000000..dcb7ec4df28e --- /dev/null +++ b/configparser34.py @@ -0,0 +1,1253 @@ +"""Configuration file parser. + +A configuration file consists of sections, lead by a "[section]" header, +and followed by "name: value" entries, with continuations and such in +the style of RFC 822. + +Intrinsic defaults can be specified by passing them into the +ConfigParser constructor as a dictionary. + +class: + +ConfigParser -- responsible for parsing a list of + configuration files, and managing the parsed database. + + methods: + + __init__(defaults=None, dict_type=_default_dict, allow_no_value=False, + delimiters=('=', ':'), comment_prefixes=('#', ';'), + inline_comment_prefixes=None, strict=True, + empty_lines_in_values=True): + Create the parser. When `defaults' is given, it is initialized into the + dictionary or intrinsic defaults. The keys must be strings, the values + must be appropriate for %()s string interpolation. + + When `dict_type' is given, it will be used to create the dictionary + objects for the list of sections, for the options within a section, and + for the default values. + + When `delimiters' is given, it will be used as the set of substrings + that divide keys from values. + + When `comment_prefixes' is given, it will be used as the set of + substrings that prefix comments in empty lines. Comments can be + indented. + + When `inline_comment_prefixes' is given, it will be used as the set of + substrings that prefix comments in non-empty lines. + + When `strict` is True, the parser won't allow for any section or option + duplicates while reading from a single source (file, string or + dictionary). Default is True. + + When `empty_lines_in_values' is False (default: True), each empty line + marks the end of an option. Otherwise, internal empty lines of + a multiline option are kept as part of the value. + + When `allow_no_value' is True (default: False), options without + values are accepted; the value presented for these is None. + + sections() + Return all the configuration section names, sans DEFAULT. + + has_section(section) + Return whether the given section exists. + + has_option(section, option) + Return whether the given option exists in the given section. + + options(section) + Return list of configuration options for the named section. + + read(filenames, encoding=None) + Read and parse the list of named configuration files, given by + name. A single filename is also allowed. Non-existing files + are ignored. Return list of successfully read files. + + read_file(f, filename=None) + Read and parse one configuration file, given as a file object. + The filename defaults to f.name; it is only used in error + messages (if f has no `name' attribute, the string `<???>' is used). + + read_string(string) + Read configuration from a given string. + + read_dict(dictionary) + Read configuration from a dictionary. Keys are section names, + values are dictionaries with keys and values that should be present + in the section. If the used dictionary type preserves order, sections + and their keys will be added in order. Values are automatically + converted to strings. + + get(section, option, raw=False, vars=None, fallback=_UNSET) + Return a string value for the named option. All % interpolations are + expanded in the return values, based on the defaults passed into the + constructor and the DEFAULT section. Additional substitutions may be + provided using the `vars' argument, which must be a dictionary whose + contents override any pre-existing defaults. If `option' is a key in + `vars', the value from `vars' is used. + + getint(section, options, raw=False, vars=None, fallback=_UNSET) + Like get(), but convert value to an integer. + + getfloat(section, options, raw=False, vars=None, fallback=_UNSET) + Like get(), but convert value to a float. + + getboolean(section, options, raw=False, vars=None, fallback=_UNSET) + Like get(), but convert value to a boolean (currently case + insensitively defined as 0, false, no, off for False, and 1, true, + yes, on for True). Returns False or True. + + items(section=_UNSET, raw=False, vars=None) + If section is given, return a list of tuples with (name, value) for + each option in the section. Otherwise, return a list of tuples with + (section_name, section_proxy) for each section, including DEFAULTSECT. + + remove_section(section) + Remove the given file section and all its options. + + remove_option(section, option) + Remove the given option from the given section. + + set(section, option, value) + Set the given option. + + write(fp, space_around_delimiters=True) + Write the configuration state in .ini format. If + `space_around_delimiters' is True (the default), delimiters + between keys and values are surrounded by spaces. +""" + +from collections.abc import MutableMapping +from collections import OrderedDict as _default_dict, ChainMap as _ChainMap +import functools +import io +import itertools +import re +import sys +import warnings + +__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", + "NoOptionError", "InterpolationError", "InterpolationDepthError", + "InterpolationSyntaxError", "ParsingError", + "MissingSectionHeaderError", + "ConfigParser", "SafeConfigParser", "RawConfigParser", + "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] + +DEFAULTSECT = "DEFAULT" + +MAX_INTERPOLATION_DEPTH = 10 + + + +# exception classes +class Error(Exception): + """Base class for ConfigParser exceptions.""" + + def __init__(self, msg=''): + self.message = msg + Exception.__init__(self, msg) + + def __repr__(self): + return self.message + + __str__ = __repr__ + + +class NoSectionError(Error): + """Raised when no section matches a requested option.""" + + def __init__(self, section): + Error.__init__(self, 'No section: %r' % (section,)) + self.section = section + self.args = (section, ) + + +class DuplicateSectionError(Error): + """Raised when a section is repeated in an input source. + + Possible repetitions that raise this exception are: multiple creation + using the API or in strict parsers when a section is found more than once + in a single input file, string or dictionary. + """ + + def __init__(self, section, source=None, lineno=None): + msg = [repr(section), " already exists"] + if source is not None: + message = ["While reading from ", repr(source)] + if lineno is not None: + message.append(" [line {0:2d}]".format(lineno)) + message.append(": section ") + message.extend(msg) + msg = message + else: + msg.insert(0, "Section ") + Error.__init__(self, "".join(msg)) + self.section = section + self.source = source + self.lineno = lineno + self.args = (section, source, lineno) + + +class DuplicateOptionError(Error): + """Raised by strict parsers when an option is repeated in an input source. + + Current implementation raises this exception only when an option is found + more than once in a single file, string or dictionary. + """ + + def __init__(self, section, option, source=None, lineno=None): + msg = [repr(option), " in section ", repr(section), + " already exists"] + if source is not None: + message = ["While reading from ", repr(source)] + if lineno is not None: + message.append(" [line {0:2d}]".format(lineno)) + message.append(": option ") + message.extend(msg) + msg = message + else: + msg.insert(0, "Option ") + Error.__init__(self, "".join(msg)) + self.section = section + self.option = option + self.source = source + self.lineno = lineno + self.args = (section, option, source, lineno) + + +class NoOptionError(Error): + """A requested option was not found.""" + + def __init__(self, option, section): + Error.__init__(self, "No option %r in section: %r" % + (option, section)) + self.option = option + self.section = section + self.args = (option, section) + + +class InterpolationError(Error): + """Base class for interpolation-related exceptions.""" + + def __init__(self, option, section, msg): + Error.__init__(self, msg) + self.option = option + self.section = section + self.args = (option, section, msg) + + +class InterpolationMissingOptionError(InterpolationError): + """A string substitution required a setting which was not available.""" + + def __init__(self, option, section, rawval, reference): + msg = ("Bad value substitution: option {!r} in section {!r} contains " + "an interpolation key {!r} which is not a valid option name. " + "Raw value: {!r}".format(option, section, reference, rawval)) + InterpolationError.__init__(self, option, section, msg) + self.reference = reference + self.args = (option, section, rawval, reference) + + +class InterpolationSyntaxError(InterpolationError): + """Raised when the source text contains invalid syntax. + + Current implementation raises this exception when the source text into + which substitutions are made does not conform to the required syntax. + """ + + +class InterpolationDepthError(InterpolationError): + """Raised when substitutions are nested too deeply.""" + + def __init__(self, option, section, rawval): + msg = ("Recursion limit exceeded in value substitution: option {!r} " + "in section {!r} contains an interpolation key which " + "cannot be substituted in {} steps. Raw value: {!r}" + "".format(option, section, MAX_INTERPOLATION_DEPTH, + rawval)) + InterpolationError.__init__(self, option, section, msg) + self.args = (option, section, rawval) + + +class ParsingError(Error): + """Raised when a configuration file does not follow legal syntax.""" + + def __init__(self, source=None, filename=None): + # Exactly one of `source'/`filename' arguments has to be given. + # `filename' kept for compatibility. + if filename and source: + raise ValueError("Cannot specify both `filename' and `source'. " + "Use `source'.") + elif not filename and not source: + raise ValueError("Required argument `source' not given.") + elif filename: + source = filename + Error.__init__(self, 'Source contains parsing errors: %r' % source) + self.source = source + self.errors = [] + self.args = (source, ) + + @property + def filename(self): + """Deprecated, use `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in future versions. " + "Use 'source' instead.", + DeprecationWarning, stacklevel=2 + ) + return self.source + + @filename.setter + def filename(self, value): + """Deprecated, user `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in future versions. " + "Use 'source' instead.", + DeprecationWarning, stacklevel=2 + ) + self.source = value + + def append(self, lineno, line): + self.errors.append((lineno, line)) + self.message += '\n\t[line %2d]: %s' % (lineno, line) + + +class MissingSectionHeaderError(ParsingError): + """Raised when a key-value pair is found before any section header.""" + + def __init__(self, filename, lineno, line): + Error.__init__( + self, + 'File contains no section headers.\nfile: %r, line: %d\n%r' % + (filename, lineno, line)) + self.source = filename + self.lineno = lineno + self.line = line + self.args = (filename, lineno, line) + + +# Used in parser getters to indicate the default behaviour when a specific +# option is not found it to raise an exception. Created to enable `None' as +# a valid fallback value. +_UNSET = object() + + +class Interpolation: + """Dummy interpolation that passes the value through with no changes.""" + + def before_get(self, parser, section, option, value, defaults): + return value + + def before_set(self, parser, section, option, value): + return value + + def before_read(self, parser, section, option, value): + return value + + def before_write(self, parser, section, option, value): + return value + + +class BasicInterpolation(Interpolation): + """Interpolation as implemented in the classic ConfigParser. + + The option values can contain format strings which refer to other values in + the same section, or values in the special default section. + + For example: + + something: %(dir)s/whatever + + would resolve the "%(dir)s" to the value of dir. All reference + expansions are done late, on demand. If a user needs to use a bare % in + a configuration file, she can escape it by writing %%. Other % usage + is considered a user error and raises `InterpolationSyntaxError'.""" + + _KEYCRE = re.compile(r"%\(([^)]+)\)s") + + def before_get(self, parser, section, option, value, defaults): + L = [] + self._interpolate_some(parser, option, L, value, section, defaults, 1) + return ''.join(L) + + def before_set(self, parser, section, option, value): + tmp_value = value.replace('%%', '') # escaped percent signs + tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax + if '%' in tmp_value: + raise ValueError("invalid interpolation syntax in %r at " + "position %d" % (value, tmp_value.find('%'))) + return value + + def _interpolate_some(self, parser, option, accum, rest, section, map, + depth): + rawval = parser.get(section, option, raw=True, fallback=rest) + if depth > MAX_INTERPOLATION_DEPTH: + raise InterpolationDepthError(option, section, rawval) + while rest: + p = rest.find("%") + if p < 0: + accum.append(rest) + return + if p > 0: + accum.append(rest[:p]) + rest = rest[p:] + # p is no longer used + c = rest[1:2] + if c == "%": + accum.append("%") + rest = rest[2:] + elif c == "(": + m = self._KEYCRE.match(rest) + if m is None: + raise InterpolationSyntaxError(option, section, + "bad interpolation variable reference %r" % rest) + var = parser.optionxform(m.group(1)) + rest = rest[m.end():] + try: + v = map[var] + except KeyError: + raise InterpolationMissingOptionError( + option, section, rawval, var) + if "%" in v: + self._interpolate_some(parser, option, accum, v, + section, map, depth + 1) + else: + accum.append(v) + else: + raise InterpolationSyntaxError( + option, section, + "'%%' must be followed by '%%' or '(', " + "found: %r" % (rest,)) + + +class ExtendedInterpolation(Interpolation): + """Advanced variant of interpolation, supports the syntax used by + `zc.buildout'. Enables interpolation between sections.""" + + _KEYCRE = re.compile(r"\$\{([^}]+)\}") + + def before_get(self, parser, section, option, value, defaults): + L = [] + self._interpolate_some(parser, option, L, value, section, defaults, 1) + return ''.join(L) + + def before_set(self, parser, section, option, value): + tmp_value = value.replace('$$', '') # escaped dollar signs + tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax + if '$' in tmp_value: + raise ValueError("invalid interpolation syntax in %r at " + "position %d" % (value, tmp_value.find('$'))) + return value + + def _interpolate_some(self, parser, option, accum, rest, section, map, + depth): + rawval = parser.get(section, option, raw=True, fallback=rest) + if depth > MAX_INTERPOLATION_DEPTH: + raise InterpolationDepthError(option, section, rawval) + while rest: + p = rest.find("$") + if p < 0: + accum.append(rest) + return + if p > 0: + accum.append(rest[:p]) + rest = rest[p:] + # p is no longer used + c = rest[1:2] + if c == "$": + accum.append("$") + rest = rest[2:] + elif c == "{": + m = self._KEYCRE.match(rest) + if m is None: + raise InterpolationSyntaxError(option, section, + "bad interpolation variable reference %r" % rest) + path = m.group(1).split(':') + rest = rest[m.end():] + sect = section + opt = option + try: + if len(path) == 1: + opt = parser.optionxform(path[0]) + v = map[opt] + elif len(path) == 2: + sect = path[0] + opt = parser.optionxform(path[1]) + v = parser.get(sect, opt, raw=True) + else: + raise InterpolationSyntaxError( + option, section, + "More than one ':' found: %r" % (rest,)) + except (KeyError, NoSectionError, NoOptionError): + raise InterpolationMissingOptionError( + option, section, rawval, ":".join(path)) + if "$" in v: + self._interpolate_some(parser, opt, accum, v, sect, + dict(parser.items(sect, raw=True)), + depth + 1) + else: + accum.append(v) + else: + raise InterpolationSyntaxError( + option, section, + "'$' must be followed by '$' or '{', " + "found: %r" % (rest,)) + + +class LegacyInterpolation(Interpolation): + """Deprecated interpolation used in old versions of ConfigParser. + Use BasicInterpolation or ExtendedInterpolation instead.""" + + _KEYCRE = re.compile(r"%\(([^)]*)\)s|.") + + def before_get(self, parser, section, option, value, vars): + rawval = value + depth = MAX_INTERPOLATION_DEPTH + while depth: # Loop through this until it's done + depth -= 1 + if value and "%(" in value: + replace = functools.partial(self._interpolation_replace, + parser=parser) + value = self._KEYCRE.sub(replace, value) + try: + value = value % vars + except KeyError as e: + raise InterpolationMissingOptionError( + option, section, rawval, e.args[0]) + else: + break + if value and "%(" in value: + raise InterpolationDepthError(option, section, rawval) + return value + + def before_set(self, parser, section, option, value): + return value + + @staticmethod + def _interpolation_replace(match, parser): + s = match.group(1) + if s is None: + return match.group() + else: + return "%%(%s)s" % parser.optionxform(s) + + +class RawConfigParser(MutableMapping): + """ConfigParser that does not do interpolation.""" + + # Regular expressions for parsing section headers and options + _SECT_TMPL = r""" + \[ # [ + (?P<header>[^]]+) # very permissive! + \] # ] + """ + _OPT_TMPL = r""" + (?P<option>.*?) # very permissive! + \s*(?P<vi>{delim})\s* # any number of space/tab, + # followed by any of the + # allowed delimiters, + # followed by any space/tab + (?P<value>.*)$ # everything up to eol + """ + _OPT_NV_TMPL = r""" + (?P<option>.*?) # very permissive! + \s*(?: # any number of space/tab, + (?P<vi>{delim})\s* # optionally followed by + # any of the allowed + # delimiters, followed by any + # space/tab + (?P<value>.*))?$ # everything up to eol + """ + # Interpolation algorithm to be used if the user does not specify another + _DEFAULT_INTERPOLATION = Interpolation() + # Compiled regular expression for matching sections + SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE) + # Compiled regular expression for matching options with typical separators + OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE) + # Compiled regular expression for matching options with optional values + # delimited using typical separators + OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE) + # Compiled regular expression for matching leading whitespace in a line + NONSPACECRE = re.compile(r"\S") + # Possible boolean values in the configuration. + BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True, + '0': False, 'no': False, 'false': False, 'off': False} + + def __init__(self, defaults=None, dict_type=_default_dict, + allow_no_value=False, *, delimiters=('=', ':'), + comment_prefixes=('#', ';'), inline_comment_prefixes=None, + strict=True, empty_lines_in_values=True, + default_section=DEFAULTSECT, + interpolation=_UNSET): + + self._dict = dict_type + self._sections = self._dict() + self._defaults = self._dict() + self._proxies = self._dict() + self._proxies[default_section] = SectionProxy(self, default_section) + if defaults: + for key, value in defaults.items(): + self._defaults[self.optionxform(key)] = value + self._delimiters = tuple(delimiters) + if delimiters == ('=', ':'): + self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE + else: + d = "|".join(re.escape(d) for d in delimiters) + if allow_no_value: + self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d), + re.VERBOSE) + else: + self._optcre = re.compile(self._OPT_TMPL.format(delim=d), + re.VERBOSE) + self._comment_prefixes = tuple(comment_prefixes or ()) + self._inline_comment_prefixes = tuple(inline_comment_prefixes or ()) + self._strict = strict + self._allow_no_value = allow_no_value + self._empty_lines_in_values = empty_lines_in_values + self.default_section=default_section + self._interpolation = interpolation + if self._interpolation is _UNSET: + self._interpolation = self._DEFAULT_INTERPOLATION + if self._interpolation is None: + self._interpolation = Interpolation() + + def defaults(self): + return self._defaults + + def sections(self): + """Return a list of section names, excluding [DEFAULT]""" + # self._sections will never have [DEFAULT] in it + return list(self._sections.keys()) + + def add_section(self, section): + """Create a new section in the configuration. + + Raise DuplicateSectionError if a section by the specified name + already exists. Raise ValueError if name is DEFAULT. + """ + if section == self.default_section: + raise ValueError('Invalid section name: %r' % section) + + if section in self._sections: + raise DuplicateSectionError(section) + self._sections[section] = self._dict() + self._proxies[section] = SectionProxy(self, section) + + def has_section(self, section): + """Indicate whether the named section is present in the configuration. + + The DEFAULT section is not acknowledged. + """ + return section in self._sections + + def options(self, section): + """Return a list of option names for the given section name.""" + try: + opts = self._sections[section].copy() + except KeyError: + raise NoSectionError(section) + opts.update(self._defaults) + return list(opts.keys()) + + def read(self, filenames, encoding=None): + """Read and parse a filename or a list of filenames. + + Files that cannot be opened are silently ignored; this is + designed so that you can specify a list of potential + configuration file locations (e.g. current directory, user's + home directory, systemwide directory), and all existing + configuration files in the list will be read. A single + filename may also be given. + + Return list of successfully read files. + """ + if isinstance(filenames, str): + filenames = [filenames] + read_ok = [] + for filename in filenames: + try: + with open(filename, encoding=encoding) as fp: + self._read(fp, filename) + except OSError: + continue + read_ok.append(filename) + return read_ok + + def read_file(self, f, source=None): + """Like read() but the argument must be a file-like object. + + The `f' argument must be iterable, returning one line at a time. + Optional second argument is the `source' specifying the name of the + file being read. If not given, it is taken from f.name. If `f' has no + `name' attribute, `<???>' is used. + """ + if source is None: + try: + source = f.name + except AttributeError: + source = '<???>' + self._read(f, source) + + def read_string(self, string, source='<string>'): + """Read configuration from a given string.""" + sfile = io.StringIO(string) + self.read_file(sfile, source) + + def read_dict(self, dictionary, source='<dict>'): + """Read configuration from a dictionary. + + Keys are section names, values are dictionaries with keys and values + that should be present in the section. If the used dictionary type + preserves order, sections and their keys will be added in order. + + All types held in the dictionary are converted to strings during + reading, including section names, option names and keys. + + Optional second argument is the `source' specifying the name of the + dictionary being read. + """ + elements_added = set() + for section, keys in dictionary.items(): + section = str(section) + try: + self.add_section(section) + except (DuplicateSectionError, ValueError): + if self._strict and section in elements_added: + raise + elements_added.add(section) + for key, value in keys.items(): + key = self.optionxform(str(key)) + if value is not None: + value = str(value) + if self._strict and (section, key) in elements_added: + raise DuplicateOptionError(section, key, source) + elements_added.add((section, key)) + self.set(section, key, value) + + def readfp(self, fp, filename=None): + """Deprecated, use read_file instead.""" + warnings.warn( + "This method will be removed in future versions. " + "Use 'parser.read_file()' instead.", + DeprecationWarning, stacklevel=2 + ) + self.read_file(fp, source=filename) + + def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): + """Get an option value for a given section. + + If `vars' is provided, it must be a dictionary. The option is looked up + in `vars' (if provided), `section', and in `DEFAULTSECT' in that order. + If the key is not found and `fallback' is provided, it is used as + a fallback value. `None' can be provided as a `fallback' value. + + If interpolation is enabled and the optional argument `raw' is False, + all interpolations are expanded in the return values. + + Arguments `raw', `vars', and `fallback' are keyword only. + + The section DEFAULT is special. + """ + try: + d = self._unify_values(section, vars) + except NoSectionError: + if fallback is _UNSET: + raise + else: + return fallback + option = self.optionxform(option) + try: + value = d[option] + except KeyError: + if fallback is _UNSET: + raise NoOptionError(option, section) + else: + return fallback + + if raw or value is None: + return value + else: + return self._interpolation.before_get(self, section, option, value, + d) + + def _get(self, section, conv, option, **kwargs): + return conv(self.get(section, option, **kwargs)) + + def getint(self, section, option, *, raw=False, vars=None, + fallback=_UNSET): + try: + return self._get(section, int, option, raw=raw, vars=vars) + except (NoSectionError, NoOptionError): + if fallback is _UNSET: + raise + else: + return fallback + + def getfloat(self, section, option, *, raw=False, vars=None, + fallback=_UNSET): + try: + return self._get(section, float, option, raw=raw, vars=vars) + except (NoSectionError, NoOptionError): + if fallback is _UNSET: + raise + else: + return fallback + + def getboolean(self, section, option, *, raw=False, vars=None, + fallback=_UNSET): + try: + return self._get(section, self._convert_to_boolean, option, + raw=raw, vars=vars) + except (NoSectionError, NoOptionError): + if fallback is _UNSET: + raise + else: + return fallback + + def items(self, section=_UNSET, raw=False, vars=None): + """Return a list of (name, value) tuples for each option in a section. + + All % interpolations are expanded in the return values, based on the + defaults passed into the constructor, unless the optional argument + `raw' is true. Additional substitutions may be provided using the + `vars' argument, which must be a dictionary whose contents overrides + any pre-existing defaults. + + The section DEFAULT is special. + """ + if section is _UNSET: + return super().items() + d = self._defaults.copy() + try: + d.update(self._sections[section]) + except KeyError: + if section != self.default_section: + raise NoSectionError(section) + # Update with the entry specific variables + if vars: + for key, value in vars.items(): + d[self.optionxform(key)] = value + value_getter = lambda option: self._interpolation.before_get(self, + section, option, d[option], d) + if raw: + value_getter = lambda option: d[option] + return [(option, value_getter(option)) for option in d.keys()] + + def popitem(self): + """Remove a section from the parser and return it as + a (section_name, section_proxy) tuple. If no section is present, raise + KeyError. + + The section DEFAULT is never returned because it cannot be removed. + """ + for key in self.sections(): + value = self[key] + del self[key] + return key, value + raise KeyError + + def optionxform(self, optionstr): + return optionstr.lower() + + def has_option(self, section, option): + """Check for the existence of a given option in a given section. + If the specified `section' is None or an empty string, DEFAULT is + assumed. If the specified `section' does not exist, returns False.""" + if not section or section == self.default_section: + option = self.optionxform(option) + return option in self._defaults + elif section not in self._sections: + return False + else: + option = self.optionxform(option) + return (option in self._sections[section] + or option in self._defaults) + + def set(self, section, option, value=None): + """Set an option.""" + if value: + value = self._interpolation.before_set(self, section, option, + value) + if not section or section == self.default_section: + sectdict = self._defaults + else: + try: + sectdict = self._sections[section] + except KeyError: + raise NoSectionError(section) + sectdict[self.optionxform(option)] = value + + def write(self, fp, space_around_delimiters=True): + """Write an .ini-format representation of the configuration state. + + If `space_around_delimiters' is True (the default), delimiters + between keys and values are surrounded by spaces. + """ + if space_around_delimiters: + d = " {} ".format(self._delimiters[0]) + else: + d = self._delimiters[0] + if self._defaults: + self._write_section(fp, self.default_section, + self._defaults.items(), d) + for section in self._sections: + self._write_section(fp, section, + self._sections[section].items(), d) + + def _write_section(self, fp, section_name, section_items, delimiter): + """Write a single section to the specified `fp'.""" + fp.write("[{}]\n".format(section_name)) + for key, value in section_items: + value = self._interpolation.before_write(self, section_name, key, + value) + if value is not None or not self._allow_no_value: + value = delimiter + str(value).replace('\n', '\n\t') + else: + value = "" + fp.write("{}{}\n".format(key, value)) + fp.write("\n") + + def remove_option(self, section, option): + """Remove an option.""" + if not section or section == self.default_section: + sectdict = self._defaults + else: + try: + sectdict = self._sections[section] + except KeyError: + raise NoSectionError(section) + option = self.optionxform(option) + existed = option in sectdict + if existed: + del sectdict[option] + return existed + + def remove_section(self, section): + """Remove a file section.""" + existed = section in self._sections + if existed: + del self._sections[section] + del self._proxies[section] + return existed + + def __getitem__(self, key): + if key != self.default_section and not self.has_section(key): + raise KeyError(key) + return self._proxies[key] + + def __setitem__(self, key, value): + # To conform with the mapping protocol, overwrites existing values in + # the section. + + # XXX this is not atomic if read_dict fails at any point. Then again, + # no update method in configparser is atomic in this implementation. + if key == self.default_section: + self._defaults.clear() + elif key in self._sections: + self._sections[key].clear() + self.read_dict({key: value}) + + def __delitem__(self, key): + if key == self.default_section: + raise ValueError("Cannot remove the default section.") + if not self.has_section(key): + raise KeyError(key) + self.remove_section(key) + + def __contains__(self, key): + return key == self.default_section or self.has_section(key) + + def __len__(self): + return len(self._sections) + 1 # the default section + + def __iter__(self): + # XXX does it break when underlying container state changed? + return itertools.chain((self.default_section,), self._sections.keys()) + + def _read(self, fp, fpname): + """Parse a sectioned configuration file. + + Each section in a configuration file contains a header, indicated by + a name in square brackets (`[]'), plus key/value options, indicated by + `name' and `value' delimited with a specific substring (`=' or `:' by + default). + + Values can span multiple lines, as long as they are indented deeper + than the first line of the value. Depending on the parser's mode, blank + lines may be treated as parts of multiline values or ignored. + + Configuration files may include comments, prefixed by specific + characters (`#' and `;' by default). Comments may appear on their own + in an otherwise empty line or may be entered in lines holding values or + section names. + """ + elements_added = set() + cursect = None # None, or a dictionary + sectname = None + optname = None + lineno = 0 + indent_level = 0 + e = None # None, or an exception + for lineno, line in enumerate(fp, start=1): + comment_start = sys.maxsize + # strip inline comments + inline_prefixes = {p: -1 for p in self._inline_comment_prefixes} + while comment_start == sys.maxsize and inline_prefixes: + next_prefixes = {} + for prefix, index in inline_prefixes.items(): + index = line.find(prefix, index+1) + if index == -1: + continue + next_prefixes[prefix] = index + if index == 0 or (index > 0 and line[index-1].isspace()): + comment_start = min(comment_start, index) + inline_prefixes = next_prefixes + # strip full line comments + for prefix in self._comment_prefixes: + if line.strip().startswith(prefix): + comment_start = 0 + break + if comment_start == sys.maxsize: + comment_start = None + value = line[:comment_start].strip() + if not value: + if self._empty_lines_in_values: + # add empty line to the value, but only if there was no + # comment on the line + if (comment_start is None and + cursect is not None and + optname and + cursect[optname] is not None): + cursect[optname].append('') # newlines added at join + else: + # empty line marks end of value + indent_level = sys.maxsize + continue + # continuation line? + first_nonspace = self.NONSPACECRE.search(line) + cur_indent_level = first_nonspace.start() if first_nonspace else 0 + if (cursect is not None and optname and + cur_indent_level > indent_level): + cursect[optname].append(value) + # a section header or option header? + else: + indent_level = cur_indent_level + # is it a section header? + mo = self.SECTCRE.match(value) + if mo: + sectname = mo.group('header') + if sectname in self._sections: + if self._strict and sectname in elements_added: + raise DuplicateSectionError(sectname, fpname, + lineno) + cursect = self._sections[sectname] + elements_added.add(sectname) + elif sectname == self.default_section: + cursect = self._defaults + else: + cursect = self._dict() + self._sections[sectname] = cursect + self._proxies[sectname] = SectionProxy(self, sectname) + elements_added.add(sectname) + # So sections can't start with a continuation line + optname = None + # no section header in the file? + elif cursect is None: + raise MissingSectionHeaderError(fpname, lineno, line) + # an option line? + else: + mo = self._optcre.match(value) + if mo: + optname, vi, optval = mo.group('option', 'vi', 'value') + if not optname: + e = self._handle_error(e, fpname, lineno, line) + optname = self.optionxform(optname.rstrip()) + if (self._strict and + (sectname, optname) in elements_added): + raise DuplicateOptionError(sectname, optname, + fpname, lineno) + elements_added.add((sectname, optname)) + # This check is fine because the OPTCRE cannot + # match if it would set optval to None + if optval is not None: + optval = optval.strip() + cursect[optname] = [optval] + else: + # valueless option handling + cursect[optname] = None + else: + # a non-fatal parsing error occurred. set up the + # exception but keep going. the exception will be + # raised at the end of the file and will contain a + # list of all bogus lines + e = self._handle_error(e, fpname, lineno, line) + # if any parsing errors occurred, raise an exception + if e: + raise e + self._join_multiline_values() + + def _join_multiline_values(self): + defaults = self.default_section, self._defaults + all_sections = itertools.chain((defaults,), + self._sections.items()) + for section, options in all_sections: + for name, val in options.items(): + if isinstance(val, list): + val = '\n'.join(val).rstrip() + options[name] = self._interpolation.before_read(self, + section, + name, val) + + def _handle_error(self, exc, fpname, lineno, line): + if not exc: + exc = ParsingError(fpname) + exc.append(lineno, repr(line)) + return exc + + def _unify_values(self, section, vars): + """Create a sequence of lookups with 'vars' taking priority over + the 'section' which takes priority over the DEFAULTSECT. + + """ + sectiondict = {} + try: + sectiondict = self._sections[section] + except KeyError: + if section != self.default_section: + raise NoSectionError(section) + # Update with the entry specific variables + vardict = {} + if vars: + for key, value in vars.items(): + if value is not None: + value = str(value) + vardict[self.optionxform(key)] = value + return _ChainMap(vardict, sectiondict, self._defaults) + + def _convert_to_boolean(self, value): + """Return a boolean value translating from other types if necessary. + """ + if value.lower() not in self.BOOLEAN_STATES: + raise ValueError('Not a boolean: %s' % value) + return self.BOOLEAN_STATES[value.lower()] + + def _validate_value_types(self, *, section="", option="", value=""): + """Raises a TypeError for non-string values. + + The only legal non-string value if we allow valueless + options is None, so we need to check if the value is a + string if: + - we do not allow valueless options, or + - we allow valueless options but the value is not None + + For compatibility reasons this method is not used in classic set() + for RawConfigParsers. It is invoked in every case for mapping protocol + access and in ConfigParser.set(). + """ + if not isinstance(section, str): + raise TypeError("section names must be strings") + if not isinstance(option, str): + raise TypeError("option keys must be strings") + if not self._allow_no_value or value: + if not isinstance(value, str): + raise TypeError("option values must be strings") + + +class ConfigParser(RawConfigParser): + """ConfigParser implementing interpolation.""" + + _DEFAULT_INTERPOLATION = BasicInterpolation() + + def set(self, section, option, value=None): + """Set an option. Extends RawConfigParser.set by validating type and + interpolation syntax on the value.""" + self._validate_value_types(option=option, value=value) + super().set(section, option, value) + + def add_section(self, section): + """Create a new section in the configuration. Extends + RawConfigParser.add_section by validating if the section name is + a string.""" + self._validate_value_types(section=section) + super().add_section(section) + + +class SafeConfigParser(ConfigParser): + """ConfigParser alias for backwards compatibility purposes.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warnings.warn( + "The SafeConfigParser class has been renamed to ConfigParser " + "in Python 3.2. This alias will be removed in future versions." + " Use ConfigParser directly instead.", + DeprecationWarning, stacklevel=2 + ) + + +class SectionProxy(MutableMapping): + """A proxy for a single section from a parser.""" + + def __init__(self, parser, name): + """Creates a view on a section of the specified `name` in `parser`.""" + self._parser = parser + self._name = name + + def __repr__(self): + return '<Section: {}>'.format(self._name) + + def __getitem__(self, key): + if not self._parser.has_option(self._name, key): + raise KeyError(key) + return self._parser.get(self._name, key) + + def __setitem__(self, key, value): + self._parser._validate_value_types(option=key, value=value) + return self._parser.set(self._name, key, value) + + def __delitem__(self, key): + if not (self._parser.has_option(self._name, key) and + self._parser.remove_option(self._name, key)): + raise KeyError(key) + + def __contains__(self, key): + return self._parser.has_option(self._name, key) + + def __len__(self): + return len(self._options()) + + def __iter__(self): + return self._options().__iter__() + + def _options(self): + if self._name != self._parser.default_section: + return self._parser.options(self._name) + else: + return self._parser.defaults() + + def get(self, option, fallback=None, *, raw=False, vars=None): + return self._parser.get(self._name, option, raw=raw, vars=vars, + fallback=fallback) + + def getint(self, option, fallback=None, *, raw=False, vars=None): + return self._parser.getint(self._name, option, raw=raw, vars=vars, + fallback=fallback) + + def getfloat(self, option, fallback=None, *, raw=False, vars=None): + return self._parser.getfloat(self._name, option, raw=raw, vars=vars, + fallback=fallback) + + def getboolean(self, option, fallback=None, *, raw=False, vars=None): + return self._parser.getboolean(self._name, option, raw=raw, vars=vars, + fallback=fallback) + + @property + def parser(self): + # The parser object of the proxy is read-only. + return self._parser + + @property + def name(self): + # The name of the section on a proxy is read-only. + return self._name diff --git a/configparser_api_changes.patch b/configparser_api_changes.patch index 04b46d9b8f21..9463840efb03 100644 --- a/configparser_api_changes.patch +++ b/configparser_api_changes.patch @@ -1,21 +1,49 @@ From: Andrew Starr-Bochicchio <asb@debian.org> +Date: Tue, 4 Jun 2019 15:33:36 +0300 Subject: Update for changes in ConfigParser api. -Bug: https://bugs.launchpad.net/kazam/+bug/1500083 -Index: kazam/kazam/backend/config.py -=================================================================== ---- kazam.orig/kazam/backend/config.py 2013-03-13 20:25:20.917993000 -0400 -+++ kazam/kazam/backend/config.py 2015-10-12 21:25:30.355157111 -0400 -@@ -98,9 +98,10 @@ +Bug: #801203, #916416, LP: #1500083 +Forwarded: https://github.com/hzbd/kazam/pull/21 +Author: Andrew Starr-Bochicchio <asb@debian.org> +Author: Sergey Spitsyn <sswebcoder@gmail.com> +Reviewed-by: Nicolas Braud-Santoni <nicoo@debian.org> +--- + kazam/backend/config.py | 10 ++++------ + 1 file changed, 4 insertions(+), 6 deletions(-) + +diff --git a/kazam/backend/config.py b/kazam/backend/config.py +index 59a6c5a..2274435 100644 +--- a/kazam/backend/config.py ++++ b/kazam/backend/config.py +@@ -73,7 +73,7 @@ class KazamConfig(ConfigParser): + CONFIGFILE = os.path.join(CONFIGDIR, "kazam.conf") + + def __init__(self): +- ConfigParser.__init__(self, self.DEFAULTS[0]['keys']) ++ super().__init__(self) + if not os.path.isdir(self.CONFIGDIR): + os.makedirs(self.CONFIGDIR) + if not os.path.isfile(self.CONFIGFILE): +@@ -98,9 +98,9 @@ class KazamConfig(ConfigParser): if d_key == key: return d_section["keys"][key] - def get(self, section, key): -+ def get(self, section, key, raw=True, fallback=None): ++ def get(self, section, key, **kwargs): try: - return ConfigParser.get(self, section, key) -+ return super(KazamConfig, self).get(section, -+ key, raw=True, fallback=fallback) ++ return super(KazamConfig, self).get(section, key, **kwargs) except NoSectionError: default = self.find_default(section, key) self.set(section, key, default) +@@ -122,9 +122,7 @@ class KazamConfig(ConfigParser): + def set(self, section, option, value): + # If the section referred to doesn't exist (rare case), + # then create it +- if not self.has_section(section): +- self.add_section(section) +- ConfigParser.set(self, section, option, str(value)) ++ super().set(section, option, str(value)) + + def write(self): + file_ = open(self.CONFIGFILE, "w") diff --git a/fix-PyGIWarnings.patch b/fix-PyGIWarnings.patch new file mode 100644 index 000000000000..06d15e54f04d --- /dev/null +++ b/fix-PyGIWarnings.patch @@ -0,0 +1,109 @@ +From: Andrew Starr-Bochicchio <asb@debian.org> +Date: Sat, 8 Jun 2019 11:01:45 +0300 +Subject: Properly import from PyGI by setting require_version + +--- + bin/kazam | 2 ++ + kazam/backend/gstreamer.py | 2 ++ + kazam/frontend/indicator.py | 6 +++++- + kazam/frontend/window_area.py | 2 ++ + 4 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/bin/kazam b/bin/kazam +index fb4073b..2382974 100755 +--- a/bin/kazam ++++ b/bin/kazam +@@ -22,6 +22,7 @@ + # MA 02110-1301, USA. + + import os ++import gi + import sys + import dbus + import logging +@@ -29,6 +30,7 @@ import dbus.service + from argparse import ArgumentParser + from dbus.mainloop.glib import DBusGMainLoop + ++gi.require_version('Gtk', '3.0') + from gi.repository import Gtk + + class KazamService(dbus.service.Object): +diff --git a/kazam/backend/gstreamer.py b/kazam/backend/gstreamer.py +index 8e35db7..f10bd10 100644 +--- a/kazam/backend/gstreamer.py ++++ b/kazam/backend/gstreamer.py +@@ -20,6 +20,7 @@ + # MA 02110-1301, USA. + + import os ++import gi + import logging + logger = logging.getLogger("GStreamer") + +@@ -32,6 +33,7 @@ import multiprocessing + os.environ["GST_DEBUG_DUMP_DOT_DIR"] = "/tmp" + os.putenv("GST_DEBUG_DUMP_DOT_DIR", "/tmp") + ++gi.require_version('Gst', '1.0') + from gi.repository import GObject, Gst + + from kazam.backend.prefs import * +diff --git a/kazam/frontend/indicator.py b/kazam/frontend/indicator.py +index d214f9b..97a3885 100644 +--- a/kazam/frontend/indicator.py ++++ b/kazam/frontend/indicator.py +@@ -24,6 +24,7 @@ import logging + logger = logging.getLogger("Indicator") + + from gettext import gettext as _ ++import gi + from gi.repository import Gtk, GObject, GLib + + from kazam.backend.prefs import * +@@ -94,6 +95,8 @@ class KazamSuperIndicator(GObject.GObject): + # Setup keybindings - Hardcore way + # + try: ++ import gi ++ gi.require_version('Keybinder', '3.0') + from gi.repository import Keybinder + logger.debug("Trying to bind hotkeys.") + Keybinder.init() +@@ -145,6 +148,7 @@ class KazamSuperIndicator(GObject.GObject): + self.emit("indicator-quit-request") + + try: ++ gi.require_version('AppIndicator3', '0.1') + from gi.repository import AppIndicator3 + + class KazamIndicator(KazamSuperIndicator): +@@ -219,7 +223,7 @@ try: + if not self.silent: + self.indicator.set_status(AppIndicator3.IndicatorStatus.ATTENTION) + +-except ImportError: ++except (ImportError, ValueError): + # + # AppIndicator failed to import, not running Ubuntu? + # Fallback to Gtk.StatusIcon. +diff --git a/kazam/frontend/window_area.py b/kazam/frontend/window_area.py +index 0024afb..2d3487b 100644 +--- a/kazam/frontend/window_area.py ++++ b/kazam/frontend/window_area.py +@@ -19,6 +19,7 @@ + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + # MA 02110-1301, USA. + ++import gi + import time + import cairo + import math +@@ -27,6 +28,7 @@ logger = logging.getLogger("Window Select") + + from gettext import gettext as _ + ++gi.require_version('Wnck', '3.0') + from gi.repository import Gtk, GObject, Gdk, Wnck, GdkX11 + + from kazam.backend.prefs import * diff --git a/setlocale.patch b/setlocale.patch new file mode 100644 index 000000000000..7c8b8339a4e0 --- /dev/null +++ b/setlocale.patch @@ -0,0 +1,25 @@ +From: Mikhail Novosyolov <mikhailnov@dumalogiya.ru> +Date: Thu, 20 Sep 2018 00:18:04 +0300 +Subject: Fix setting the locale of Kazam UI + +--- + kazam/app.py | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/kazam/app.py b/kazam/app.py +index d14face..e4e3182 100644 +--- a/kazam/app.py ++++ b/kazam/app.py +@@ -821,8 +821,11 @@ class KazamApp(GObject.GObject): + def setup_translations(self): + gettext.bindtextdomain("kazam", "/usr/share/locale") + gettext.textdomain("kazam") ++ locale.bindtextdomain("kazam", "/usr/share/locale") ++ locale.textdomain("kazam") ++ currentLocale = locale.getlocale() + try: +- locale.setlocale(locale.LC_ALL, "") ++ locale.setlocale(locale.LC_ALL, currentLocale) + except Exception as e: + logger.exception("EXCEPTION: Setlocale failed, no language support.") + diff --git a/version.patch b/version.patch deleted file mode 100644 index dbc8fc8e50b0..000000000000 --- a/version.patch +++ /dev/null @@ -1,9 +0,0 @@ -diff -urbN a/kazam/version.py b/kazam/version.py ---- a/kazam/version.py 2013-06-10 10:32:59.000000000 +0300 -+++ b/kazam/version.py 2013-06-10 10:37:42.000000000 +0300 -@@ -1,3 +1,3 @@ --DISTRO='Ubuntu' --RELEASE='14.04' -+DISTRO='Archlinux' -+RELEASE='rolling' - |