feat(Anaconda): Local Repo

This commit is contained in:
2026-06-13 02:06:34 +02:00
parent 05ae9c36c3
commit c412cd5d33
203 changed files with 23997 additions and 7 deletions
@@ -0,0 +1,14 @@
*.pyc
.*.swp
python-meh*.tar.gz
ChangeLog
po/*.gmo
po/*.po
po/POTFILES
build/
.directory
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
675 Mass Ave, Cambridge, MA 02139, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
Appendix: How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
@@ -0,0 +1,6 @@
include COPYING Makefile ChangeLog
recursive-include meh *.py
recursive-include ui *.glade
recursive-include pixmaps *.png
recursive-include po *.po *.pot Makefile
recursive-include tests *.py
@@ -0,0 +1,101 @@
PKGNAME=python-meh
VERSION=$(shell awk '/Version:/ { print $$2 }' $(PKGNAME).spec)
RELEASE=$(shell awk '/Release:/ { print $$2 }' $(PKGNAME).spec | sed -e 's|%.*$$||g')
TAG=r$(VERSION)-$(RELEASE)
PREFIX=/usr
PYTHON2=python2
PYTHON3=python3
PYTHON=$(PYTHON2)
TESTSUITE:=tests/baseclass.py
PYCHECKEROPTS=--no-argsused --no-miximport --maxargs 0 --no-local -\# 0 --only -Q
ZANATA_PULL_ARGS = --transdir po/
ZANATA_PUSH_ARGS = --srcdir po/ --push-type source --force
default: all
all:
$(MAKE) -C po
clean:
-rm *.tar.gz meh/*.pyc meh/ui/*.pyc tests/*.pyc ChangeLog
$(MAKE) -C po clean
$(PYTHON) setup.py -q clean --all
test:
@echo "*** Running unittests with Python 2 ***"
PYTHONPATH=. $(PYTHON2) $(TESTSUITE) -v
@echo "*** Running unittests with Python 3 ***"
PYTHONPATH=. $(PYTHON3) $(TESTSUITE) -v
check:
PYTHONPATH=. tests/pylint/runpylint.py
install:
$(PYTHON) setup.py install --root=$(DESTDIR)
$(MAKE) -C po install
ChangeLog:
(GIT_DIR=.git git log > .changelog.tmp && mv .changelog.tmp ChangeLog; rm -f .changelog.tmp) || (touch ChangeLog; echo 'git directory not found: installing possibly empty changelog.' >&2)
tag:
git tag -a -m "Tag as $(TAG)" -f $(TAG)
@echo "Tagged as $(TAG)"
release: tag archive
archive: po-pull
@rm -f ChangeLog
@make ChangeLog
git archive --format=tar --prefix=$(PKGNAME)-$(VERSION)/ $(TAG) > $(PKGNAME)-$(VERSION).tar
mkdir $(PKGNAME)-$(VERSION)
cp -r po $(PKGNAME)-$(VERSION)
cp ChangeLog $(PKGNAME)-$(VERSION)/
tar -rf $(PKGNAME)-$(VERSION).tar $(PKGNAME)-$(VERSION)
gzip -9 $(PKGNAME)-$(VERSION).tar
rm -rf $(PKGNAME)-$(VERSION)
git checkout -- po/$(PKGNAME).pot
@echo "The archive is in $(PKGNAME)-$(VERSION).tar.gz"
local: po-pull
@rm -f ChangeLog
@make ChangeLog
@rm -rf $(PKGNAME)-$(VERSION).tar.gz
@rm -rf /tmp/$(PKGNAME)-$(VERSION) /tmp/$(PKGNAME)
@dir=$$PWD; cp -a $$dir /tmp/$(PKGNAME)-$(VERSION)
@cd /tmp/$(PKGNAME)-$(VERSION) ; $(PYTHON) setup.py -q sdist
@cp /tmp/$(PKGNAME)-$(VERSION)/dist/$(PKGNAME)-$(VERSION).tar.gz .
@rm -rf /tmp/$(PKGNAME)-$(VERSION)
@echo "The archive is in $(PKGNAME)-$(VERSION).tar.gz"
rpmlog:
@git log --pretty="format:- %s (%ae)" $(TAG).. |sed -e 's/@.*)/)/'
@echo
potfile:
$(MAKE) -C po potfile
po-pull:
rpm -q zanata-python-client &>/dev/null || ( echo "need to run: dnf install zanata-python-client"; exit 1 )
zanata pull $(ZANATA_PULL_ARGS)
bumpver: potfile
zanata push $(ZANATA_PUSH_ARGS) || ( echo "zanata push failed"; exit 1 )
@NEWSUBVER=$$((`echo $(VERSION) |cut -d . -f 2` + 1)) ; \
NEWVERSION=`echo $(VERSION).$$NEWSUBVER |cut -d . -f 1,3` ; \
DATELINE="* `LANG=c date "+%a %b %d %Y"` `git config user.name` <`git config user.email`> - $$NEWVERSION-1" ; \
cl=`grep -n %changelog python-meh.spec |cut -d : -f 1` ; \
tail --lines=+$$(($$cl + 1)) python-meh.spec > speclog ; \
(head -n $$cl python-meh.spec ; echo "$$DATELINE" ; make --quiet rpmlog 2>/dev/null ; echo ""; cat speclog) > python-meh.spec.new ; \
mv python-meh.spec.new python-meh.spec ; rm -f speclog ; \
sed -i "s/Version: $(VERSION)/Version: $$NEWVERSION/" python-meh.spec ; \
sed -i "s/version='$(VERSION)'/version='$$NEWVERSION'/" setup.py
ci: check test
.PHONY: clean install tag archive local
@@ -0,0 +1,38 @@
#!/usr/bin/python -tbBQnew
# -*- coding: utf-8 -*-
import sys
import meh
import meh.handler
import meh.dump
import subprocess
class MyTestError(Exception):
pass
class MyObject(object):
def __init__(self):
self.unicode_str = u"řčšěčšěčěš"
self.encoded_unicode_str = self.unicode_str.encode("utf-8")
self.binary_data = '\xff\xff\xfe'
if __name__ == "__main__":
print("***Running python-meh test***")
config = meh.Config(programName="myMehTest", programVersion="1.0-1",
programArch="noarch",
fileList=["/proc/cmdline", "unicode.txt", "ascii.txt"])
if len(sys.argv) > 1 and sys.argv[1] == "g":
import meh.ui.gui
intf = meh.ui.gui.GraphicalIntf()
else:
import meh.ui.text
intf = meh.ui.text.TextIntf()
handler = meh.handler.ExceptionHandler(config, intf,
meh.dump.ExceptionDump)
handler.install(MyObject())
#raise MyTestError("My testing exception.")
subprocess.call(["non_existing_executable"])
@@ -0,0 +1,139 @@
#
# __init__.py - Basic stuff for exception handling
#
# Copyright (C) 2009 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Chris Lumens <clumens@redhat.com>
#
from collections import namedtuple
# These constants represent the return values of buttons on the initial
# exception handling dialog - the dialog that first pops up when an
# exception is hit.
MAIN_RESPONSE_DEBUG = 0
MAIN_RESPONSE_SAVE = 1
MAIN_RESPONSE_QUIT = 2
MAIN_RESPONSE_SHELL = 3
MAIN_RESPONSE_NONE = 4
class ConfigError(Exception):
"""Exception class for the configuration related errors."""
pass
class Config(object):
"""Hold configuration info useful throughout the exception handling
classes. This prevents having to pass a bunch of arguments to a
bunch of different functions. A Config instance must be created
before creating an ExceptionHandler instance.
"""
def __init__(self, *args, **kwargs):
"""Create a new Config instance. Arguments may be passed in to
set any of the configuration values. Unknown arguments will
be ignored. Instance attributes:
attrSkipList -- A list of strings. When handling a traceback,
any attributes found with the same name as an
element of this list will not be written to
the dump. This is to prevent writing potentially
sensitive information like passwords. The names
must be given without the leading name of the
object passed to handler.install(). For instance,
if handler.install() gets an Anaconda instance
with the name "anaconda" and you want to skip
anaconda.id.rootPassword, "id.rootPassword" should
be listed in attrSkipList.
fileList -- A list of files to find on the system and add
to the traceback dump.
localSkipList -- A list of strings. When handling a traceback,
any local variables found with their name containing
an element of this list will not be written to
the dump. This is subtely different from
attrSkipList.
callbackDict -- A dictionary having item names as keys and pairs
(function, attchmnt_only) as values.
@see register_callback
programName -- The name of the erroring program.
programVersion -- The version number of the erroring program.
Both programName and programVersion are used
throughout the exception handler, so must be
set.
programArch -- The architecture of the erroring program.
"""
self.attrSkipList = []
self.fileList = []
self.localSkipList = []
self.callbackDict = dict()
self.programName = None
self.programVersion = None
self.programArch = None
# Override the defaults set above with whatever's passed in as an
# argument. Unknown arguments get thrown away.
for (key, val) in kwargs.items():
if key in self.__dict__:
self.__dict__[key] = val
# Make sure required things are set.
if not self.programName:
raise ValueError("programName must be set.")
if not self.programVersion:
raise ValueError("programVersion must be set.")
def register_callback(self, item_name, callback, attchmnt_only=False,
override=False):
"""
Register new callback that will be called when data about the
crash is collected. The returned value will be included as the
'item_name' item.
:param item_name: name of the item storing the value returned by the
'callback' function
:type item_name: string
:param callback: a function to be called
:type callback: a function of type: () -> string
:param attchmnt_only: whether the returned valued should be included only
as an attachment or also in the main '*-tb' file
(defaults to False)
:type attchmnt_only: bool
:param override: whether to override the previously registered callback
with the same item name or not (may raise exception)
:type override: bool
:raise ConfigError: if callback with the 'item_name' has already been
registered and 'override' is not set to True
:return: None
"""
if item_name not in self.callbackDict or override:
self.callbackDict[item_name] = (callback, attchmnt_only)
else:
msg = "Callback with name '%s' already registered" % item_name
raise ConfigError(msg)
# type, value and stack are items provided by Python for the exception handler
ExceptionInfo = namedtuple("ExceptionInfo", ["type", "value", "stack"])
# ExceptionInfo instance plus the object that should be dumped
DumpInfo = namedtuple("DumpInfo", ["exc_info", "object"])
# information about a package (as provided e.g. by rpm)
PackageInfo = namedtuple("PackageInfo", ["name", "version", "release", "epoch",
"arch"])
@@ -0,0 +1,551 @@
#
# dump.py - general exception formatting and saving
#
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2009 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Matt Wilson <msw@redhat.com>
# Erik Troan <ewt@redhat.com>
# Chris Lumens <clumens@redhat.com>
#
# pylint: disable=bare-except,broad-except
import copy
import inspect
import os
import traceback
import sys
import codecs
import six
from meh import PackageInfo
from meh.safe_string import SafeStr
# Python 2/3 compatibilty
try:
longtype = long # pylint: disable=undefined-variable
except NameError:
longtype = int
try:
unitype = unicode # pylint: disable=undefined-variable
except NameError:
unitype = str
class ExceptionDump(object):
"""This class represents a traceback and contains several useful methods
for manipulating a traceback. In general, clients should not have to
use this class. It is mainly for internal use.
"""
def __init__(self, exc_info, config_obj):
"""Create a new ExceptionDump instance. Instance attributes:
:param exc_info: info about the exception provided by Python
:type exc_info: an instance of the meh.ExceptionInfo class
:param config_obj: configuration for python-meh
:type config_obj: an instance of the meh.Config class
"""
if inspect.istraceback(exc_info.stack):
self.stack = inspect.getinnerframes(exc_info.stack)
else:
self.stack = exc_info.stack
self.conf = config_obj
self.type = exc_info.type
self.value = exc_info.value
self._dumpHash = {}
@property
def desc(self):
# The description is a single line of text that should be used anywhere
# the bug needs to quickly be summarized. The most obvious example of
# this is when saving to bugzilla. We can populate the UI with this
# string so the user doesn't need to come up with one.
if self.type and self.value:
return traceback.format_exception_only(self.type, self.value)[0].strip()
else:
return ""
def _get_environment_info(self):
"""
Returns dictionary containing these items for a bugreport header
architecture
cmdline
component
executable
kernel
package
release
other involved packages
:rtype: dict (string -> string)
"""
RELEASE_NAME_FILE = "/etc/system-release"
class RPMinfoError(Exception):
"""Exception for errors in rpm queries"""
pass
def get_python_opts():
"""
Returns python's command line options
:return: list of python's command line options
:rtype: list
"""
flags_to_opts = {
"debug": "-d",
"py3k_warning": "-3",
"division_new": "-Qnew",
"division_warning": "-Q",
"inspect": "-i",
"interactive": "-i",
"optimize": "-O",
"dont_write_bytecode": "-B",
"no_user_site": "-s",
"no_site": "-S",
"ignore_environment": "-E",
"tabcheck": "-t",
"verbose": "-v",
"unicode": "-u",
"bytes_warning": "-b",
"quiet": "-q",
}
ret = set()
for flag in flags_to_opts.keys():
if getattr(sys.flags, flag, None):
ret.add(flags_to_opts[flag])
ret = list(ret)
return ret
def get_package_and_component(file_=sys.argv[0]):
"""
Returns package and component names for file (by default for script itself).
:param file_: filename
:type file_: string
:return: tuple containing package info and component name for the file
:rtype: (PackageInfo, str)
:raise RPMinfoError: if package and component for the file cannot be found
"""
import rpm
ts = rpm.TransactionSet()
mi = ts.dbMatch("basenames", file_)
try:
header = next(mi)
except StopIteration:
raise RPMinfoError("Cannot get package and component for file "+
"{0}".format(file_))
pkg_info = PackageInfo(header["name"].decode("utf-8"), header["version"].decode("utf-8"),
header["release"].decode("utf-8"),
u"%d" % header["epoch"] if header["epoch"] else u"0",
header["arch"].decode("utf-8"))
# cuts the name from the NVR format: foo-blah-2.8.8-2.fc17.src.rpm
srpm_name = header["sourcerpm"].decode("utf-8")
name_end = len(srpm_name)
try:
name_end = srpm_name.rindex('-')
name_end = srpm_name[:name_end].rindex('-')
except ValueError:
# expected exception
pass
component = srpm_name[:name_end]
return (pkg_info, component)
def get_release_version():
"""Returns release version (according to RELEASE_NAME_FILE)"""
try:
with open(RELEASE_NAME_FILE, "r") as file_:
return file_.readline().rstrip()
except IOError:
return "Cannot get release name."
def get_other_packages(self):
"""
Returns a set of additional packages whose files appear in
traceback.
:rtype: set
"""
packages = set()
if not self.stack:
return packages
for (_frame, fn, _lineno, _func, _ctx, _idx) in self.stack:
try:
pkg_info = get_package_and_component(fn)[0]
package = "{0.name}-{0.version}-{0.release}.{0.arch}".format(
pkg_info)
packages.add(package)
except RPMinfoError:
continue
try:
pkg_info = get_package_and_component()[0]
package = "{0.name}-{0.version}-{0.release}.{0.arch}".format(
pkg_info)
packages.discard(package)
except RPMinfoError:
pass
return packages
def get_environment_variables():
"""
Returns a list of strings containing defined environment
variables and their values in the following format:
VARIABLE=VALUE
:rtype: list
"""
ret = list()
for (key, value) in os.environ.items():
ret.append("{0}={1}".format(key, value))
return ret
#--begining of the method _get_environment_info--
try:
pkg_info, component = get_package_and_component()
except RPMinfoError:
pkg_info = None
component = None
other_packages = ", ".join(get_other_packages(self))
ret = dict()
ret["architecture"] = os.uname()[4]
ret["cmdline"] = "{0} {1} {2}".format(sys.executable,
" ".join(get_python_opts()),
" ".join(sys.argv))
if component:
ret["component"] = component
ret["executable"] = sys.argv[0]
ret["kernel"] = os.uname()[2]
if pkg_info:
ret["pkg_info"] = pkg_info
ret["release"] = get_release_version()
if other_packages:
ret["other involved packages"] = other_packages
ret["environ"] = "\n".join(get_environment_variables())
return ret
@property
def environment_info(self):
return self._get_environment_info()
def __str__(self):
lst = self._format_stack()
lst.insert(0, "%s %s exception report\n" % (self.conf.programName, self.conf.programVersion))
lst.insert(1, "Traceback (most recent call last):\n")
if self.type is not None and self.value is not None:
lst.extend(traceback.format_exception_only(self.type, self.value))
return "".join(lst)
def _format_stack(self):
if not self.stack:
return []
frames = []
for (_frame, fn, lineno, func, ctx, _idx) in self.stack:
if isinstance(ctx, list):
code = "".join(ctx)
else:
code = ctx
frames.append((fn, lineno, func, code))
return traceback.format_list(frames)
# Create a string representation of a class. This method will recursively
# handle all attributes of the base given class.
def _dumpClass(self, instance, level=0, parentkey="", skipList=None):
# This is horribly cheesy, and bound to require maintainence in the
# future. The problem is that anything subclassed from object fails
# the types.InstanceType test we used to have which means those
# objects never get dumped. However, most everything basic in
# python is derived from object so we can't just check for the super
# class. Any instances of the following types will just be printed
# out, and everything else will be assumed to be something that
# needs to be recursed on.
def __isSimpleType(instance):
return isinstance(instance, bool) or \
isinstance(instance, complex) or \
isinstance(instance, float) or \
isinstance(instance, int) or \
isinstance(instance, longtype) or \
isinstance(instance, type(None)) or \
isinstance(instance, bytes) or \
isinstance(instance, str) or \
isinstance(instance, unitype) or \
not hasattr(instance, "__class__") or \
not hasattr(instance, "__dict__")
ret = SafeStr()
if not skipList:
skipList = []
# protect from loops
try:
# Store the id(), not the instance name to protect against
# instances that cannot be hashed.
if not id(instance) in self._dumpHash:
self._dumpHash[id(instance)] = None
else:
ret += "Already dumped (%s instance)\n" % instance.__class__.__name__
return ret
except TypeError:
ret += "Cannot dump object\n"
return ret
if ("__str__" in instance.__class__.__dict__ or
"__repr__" in instance.__class__.__dict__):
try:
ret += "%s\n" % (instance,)
except:
ret += "\n"
return ret
ret += "%s instance, containing members:\n" %\
(instance.__class__.__name__)
pad = ' ' * ((level) * 2)
for key, value in instance.__dict__.items():
if key.startswith("_%s__" % instance.__class__.__name__):
continue
if parentkey != "":
curkey = parentkey + "." + key
else:
curkey = key
# Don't dump objects that are in our skip list, though ones that are
# None are probably okay.
if eval("instance.%s is not None" % key) and \
eval("id(instance.%s)" % key) in skipList:
ret += "%s%s: Skipped\n" % (pad, curkey)
continue
if isinstance(value, list):
ret += "%s%s: [" % (pad, curkey)
first = 1
for item in value:
if not first:
ret += ", "
else:
first = 0
if __isSimpleType(item):
ret += "%s" % (item,)
else:
ret += self._dumpClass(item, level + 1, skipList=skipList)
ret += "]\n"
elif isinstance(value, dict):
# append things one after another so that e.g. binary data is
# replaced by hexa values separately
ret += pad
ret += curkey
ret += ": {"
first = 1
for k, v in value.items():
if not first:
ret += ", "
else:
first = 0
if isinstance(k, str) or isinstance(k, unitype):
ret += "'"
ret += k
ret += "': "
else:
ret += "%s: " % (k,)
if __isSimpleType(v):
ret += "%s" % (v,)
else:
ret += self._dumpClass(v, level + 1, parentkey = curkey, skipList=skipList)
ret += "}\n"
elif __isSimpleType(value):
ret += "%s%s: " % (pad, curkey)
ret += value
ret += "\n"
else:
ret += "%s%s: " % (pad, curkey)
ret += self._dumpClass(value, level + 1, parentkey=curkey, skipList=skipList)
return ret
def dump(self, obj):
"""Dump the local variables and all attributes of a given object.
The lists of files and attrs to ignore are all taken from a
Config object instance provided when the ExceptionDump class was
created.
obj -- Any Python object. This object will have all its attributes
written out, except for those mentioned in the attrSkipList.
"""
idSkipList = []
ret = SafeStr()
# We need to augment the environment eval() will run in with the
# bindings that were local when the traceback happened so that the
# idSkipList can be populated. However since we're not allowed to
# modify the results of locals(), we'll have to make a copy first.
localVars = copy.copy(locals())
if self.stack:
localVars.update(self.stack[-1][0].f_locals)
# Catch attributes that do not exist at the time we do the exception dump
# and ignore them.
for k in self.conf.attrSkipList:
try:
eval("idSkipList.append(id(obj.%s))" % k, None, localVars)
except:
pass
# Write local variables to the given file descriptor, ignoring any of
# the local skips.
if self.stack:
frame = self.stack[-1][0]
ret += "\nLocal variables in innermost frame:\n"
try:
for (key, value) in frame.f_locals.items():
lowered_key = key.lower()
if any(item.lower() in lowered_key
for item in self.conf.localSkipList):
continue
ret += "%s: %s\n" % (key, value)
except:
pass
# And now dump the object's attributes.
try:
ret += "\n\n"
ret += self._dumpClass(obj, skipList=idSkipList)
except:
ret += "\nException occurred during state dump:\n"
ret += traceback.format_exc(None)
# Filter out item names and callbacks that should appear
# only as attachments
items_callbacks = ((name, cb) for (name, (cb, attchmnt_only))
in self.conf.callbackDict.items()
if not attchmnt_only)
# And now add data returned by the registered callbacks
ret += "\nRegistered callbacks:\n"
for (item_name, callback) in items_callbacks:
try:
callback_ret = callback()
if callback_ret:
ret += "%s:\n%s\n" % (item_name, callback_ret)
except Exception as exc:
ret += "%s: Caused error: %s\n" % (item_name, exc)
# And finally, write a bunch of files into the dump too.
for fname in self.conf.fileList:
try:
if six.PY2:
with codecs.open(fname, "r", "utf-8", "ignore") as fobj:
ret += "\n\n%s:\n" % (fname,)
for line in fobj:
ret += line.encode("utf-8")
else:
with open(fname, "rt", encoding="utf-8") as fobj:
ret += "\n\n%s:\n" % (fname,)
for line in fobj:
ret += line
except IOError:
pass
except:
ret += "\nException occurred during %s file copy:\n" % (fname,)
ret += traceback.format_exc(None)
return ret
@property
def hash(self):
"""Create a hash for this traceback object. This is most suitable for
searching bug filing systems for duplicates. The hash is composed
of the basename of each file in the stack, the method names, and
the bit of code. Line numbers and the actual exception message
itself are left out.
"""
import hashlib
s = ""
if self.stack:
for (file, _lineno, func, text) in [f[1:5] for f in self.stack]:
if isinstance(text, list):
text = "".join(text)
s += "%s %s %s\n" % (os.path.basename(file), func, text)
else:
s = "%s %s" % (self.type, self.value)
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def traceback_and_object_dump(self, obj):
"""
Return the traceback and a text representation of an object.
:param obj: Any Python object. This object will have all its attributes
written out, except for those mentioned in the attrSkipList.
"""
ret = SafeStr(str(self))
ret += self.dump(obj)
return ret
class ReverseExceptionDump(ExceptionDump):
"""This class provides an alternate representation of an exception. In
this representation, the traceback is printed with the most recent call
at the top of the stack, and the most generic call at the bottom. Note
that this order does not affect the hash at all.
"""
def __str__(self):
lst = self._format_stack()
lst.reverse()
lst.insert(0, "%s %s exception report\n" % (self.conf.programName, self.conf.programVersion))
lst.insert(1, "Traceback (most recent call first):\n")
if self.type is not None and self.value is not None:
lst.extend(traceback.format_exception_only(self.type, self.value))
return "".join(lst)
@@ -0,0 +1,299 @@
# Copyright (C) 2009 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Chris Lumens <clumens@redhat.com>
#
from meh import MAIN_RESPONSE_DEBUG, MAIN_RESPONSE_SAVE, MAIN_RESPONSE_SHELL, MAIN_RESPONSE_QUIT, DumpInfo, ExceptionInfo
import bdb
import os
import sys
import report
import traceback
import subprocess
import gettext
_ = lambda x: gettext.ldgettext("python-meh", x)
class NoNetwork(Exception):
def __init__(self, msg=""):
Exception.__init__(self)
self.msg = msg
def __str__(self):
return "No network is available"
class ExceptionHandler(object):
def __init__(self, confObj, intfClass, exnClass):
"""Create a new ExceptionHandler instance. Subclasses of this one may
be created, provided they are careful to provide all the methods
this one does. Instance attributes:
conf -- A filled in Config instance. At least the programName
and programVersion are required.
exnClass -- An instance of ExceptionDump or a subclass of it. This
is required to know how to represent the Python
exception internally.
intfClass-- An instance of AbstractIntf. This is required to know
what UI classes to use.
"""
self.conf = confObj
self.exnClass = exnClass
self.intf = intfClass
self._exitcode = 10
self._exn = None
self.exnFile = None
self.exnText = ""
def _setExitCode(self, code):
self._exitcode = code
exitcode = property(lambda s: s._exitcode,
lambda s, v: s._setExitCode(v))
def _setExn(self, e):
self._exn = e
exn = property(lambda s: s._exn,
lambda s, v: s._setExn(v))
def handleException(self, dump_info):
"""This is the main exception handling entry point. When Python
gets an exception it doesn't know how to handle, this method will
be called. It then saves the traceback and displays the main
dialog asking the user what to do next. Once this method is
called, there's no good way to go back to what you were doing
before.
All arguments are passed in from the handler created by calling
self.install(). This method should not usually be overridden by
a subclass.
:type dump_info: an instance of the meh.DumpInfo class
"""
responseHash = { MAIN_RESPONSE_QUIT: self.runQuit,
MAIN_RESPONSE_DEBUG: self.runDebug,
MAIN_RESPONSE_SAVE: self.runSave,
MAIN_RESPONSE_SHELL: self.runShell,
}
# Quit if we got an exception when running pdb.
if isinstance(dump_info.exc_info.value, bdb.BdbQuit):
sys.exit(self.exitcode)
# Restore original exception handler.
sys.excepthook = sys.__excepthook__
self.preWriteHook(dump_info)
# Save the exception to the filesystem first.
self.exn = self.exnClass(dump_info.exc_info, self.conf)
(fobj, self.exnFile) = self.openFile()
self.exnText = self.exn.traceback_and_object_dump(dump_info.object)
fobj.write(self.exnText)
fobj.close()
self.postWriteHook(dump_info)
# Run initial UI screen, asking whether to save/debug/quit.
while True:
win = self.intf.mainExceptionWindow(str(self.exn), self.exnText)
if not win:
self.runQuit(dump_info.exc_info)
rc = win.run()
win.destroy()
try:
responseHash[rc](dump_info.exc_info)
except KeyError:
# This happens if (for example) the user hits Escape instead
# of pressing a button.
continue
win.destroy()
def preWriteHook(self, dump_info):
"""Subclasses may supply a function with this name that will be
called immediately before the traceback is written to disk in
order to have any sort of special pre-write processing that needs
to be done.
:type dump_info: an instance of the meh.DumpInfo class
"""
pass
def postWriteHook(self, dump_info):
"""Subclasses may supply a function with this name that will be
called immediately after the traceback is written to disk, but
immediately before the UI is run. This is to provide a place for
any special handling to happen once there is a file on disk.
:type dump_info: an instance of the meh.DumpInfo class
"""
pass
def install(self, obj):
"""Install ourselves as the top level exception handler with Python.
If this method is not called after an ExceptionHandler instance is
created, none of the rest of this code will ever be called.
obj -- A Python object that may be dumped to a file when the
exception is saved. This should be something like the top
level object in a program.
"""
sys.excepthook = lambda ty, value, tb: \
self.handleException(DumpInfo(ExceptionInfo(ty, value, tb), obj))
def openFile(self):
"""Create a randomly named output file to write the exception dump to.
This requires a programName be set in the Config instance. The
return value is a (file descriptor, path) pair. The file must be
closed by the caller when writing is done. Subclasses should not
override this method unless they know what they're doing.
"""
import tempfile
(fd, path) = tempfile.mkstemp("", "%s-tb-" % self.conf.programName, "/tmp")
fo = os.fdopen(fd, "w")
return (fo, path)
### Methods called by the primary exception handling dialog.
def runQuit(self, exc_info):
"""This method is called when the "Exit" button is clicked. It may
be overridden by a subclass, but the basic behavior of eventually
quitting should be preserved.
:type exc_info: an instance of the meh.ExceptionInfo class
"""
sys.exit(self.exitcode)
def runDebug(self, exc_info):
"""This method is called when the "Debug" button is clicked. It may
be overridden by a subclass if specialized behavior is required to
enter debug mode.
:type exc_info: an instance of the meh.ExceptionInfo class
"""
print("")
print(_("Use 'continue' command to quit the debugger and get back to "
"the main menu"))
import pdb
pdb.post_mortem(exc_info.stack)
#no need to quit here, let's just get back to the main dialog
def runShell(self, exc_info):
"""This method is called when user requests to run a shell. It may
be overridden by a subclass if specialized behavior is required to
run shell (e.g. TTY switching).
:type exc_info: an instance of the meh.ExceptionInfo class
"""
print("")
print(_("Exit the shell to get back to the main menu"))
proc = subprocess.Popen(["bash", "--login"], shell=True, cwd="/")
proc.wait()
def runSave(self, exc_info):
"""This method is called when the "Save" button is clicked. It may
be overridden by a subclass, but that's going to be a lot of work.
:type exc_info: an instance of the meh.ExceptionInfo class
"""
params = dict()
params.update(self.exn.environment_info)
pkg_info = params.pop("pkg_info", None)
#if rpmdb queries fail (anaconda), use self.conf.* values
if "component" not in params:
params["component"] = self.conf.programName
if pkg_info:
params["pkg_name"] = pkg_info.name
params["pkg_version"] = pkg_info.version
params["pkg_release"] = pkg_info.release
params["pkg_epoch"] = pkg_info.epoch
params["pkg_arch"] = pkg_info.arch
params["package"] = "%s-%s-%s.%s" % (pkg_info.name, pkg_info.version,
pkg_info.release, pkg_info.arch)
else:
params["pkg_name"] = self.conf.programName
parts = self.conf.programVersion.rsplit("-", 1)
if len(parts) == 2:
version, release = parts
else:
version = parts[0]
# not given, make something up
release = "1"
params["pkg_version"] = version
params["pkg_release"] = release
# we don't support specifying epoch in the config object and only
# the anaconda which is not using epochs doesn't have the RPMdb to
# query
params["pkg_epoch"] = "0"
params["pkg_arch"] = self.conf.programArch
params["package"] = "{0.programName}-{0.programVersion}".format(
self.conf)
params["hashmarkername"] = self.conf.programName
params["duphash"] = self.exn.hash
params["reason"] = self.exn.desc
params["description"] = "The following was filed automatically by %s:\n%s" \
% (self.conf.programName, str(self.exn))
tb_item_name = "%s-tb" % self.conf.programName
params[tb_item_name] = self.exnText
# save data generated by callbacks
for (item_name, (callback, _anc)) in self.conf.callbackDict.items():
try:
ret = callback()
if ret:
params[item_name] = ret
except Exception as exc: # pylint: disable=broad-except
params[item_name] = "Caused error: %s" % exc
for fpath in self.conf.fileList:
try:
with open(fpath, "r") as fobj:
# would be better to use the full path here, but libreport
# doesn't allow '/' characters in the item name
filename = os.path.basename(fpath)
if filename not in params:
params[filename] = fobj.read()
else:
params[filename+"_file"] = fobj.read()
except: # pylint: disable=bare-except
#skip files we cannot read
continue
# tell ABRT some more things it requires
params["analyzer"] = "Python"
params["backtrace"] = "".join(traceback.format_exception(exc_info.type,
exc_info.value,
exc_info.stack))
signature = report.createPythonUnhandledExceptionSignature(**params)
# We don't want to automatically quit here since the user may wish to
# save somewhere else, debug, etc.
self.intf.saveExceptionWindow(signature)
@@ -0,0 +1,45 @@
#
# network.py - network configuration install data
#
# Copyright (C) 2008, 2009 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): David Cantrell <dcantrell@redhat.com>
#
import dbus
# This code was taken from anaconda, and slightly adapted to not rely
# on isys.
NM_SERVICE = "org.freedesktop.NetworkManager"
NM_MANAGER_PATH = "/org/freedesktop/NetworkManager"
NM_STATE_CONNECTED = 3
DBUS_PROPS_IFACE = "org.freedesktop.DBus.Properties"
def hasActiveNetDev():
"""Does the system have an enabled network interface?"""
try:
bus = dbus.SystemBus()
nm = bus.get_object(NM_SERVICE, NM_MANAGER_PATH)
props = dbus.Interface(nm, DBUS_PROPS_IFACE)
state = props.Get(NM_SERVICE, "State")
if int(state) == NM_STATE_CONNECTED:
return True
else:
return False
except dbus.DBusException:
return False
@@ -0,0 +1,71 @@
#
# Copyright (C) 2013 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author: Vratislav Podzimek <vpodzime@redhat.com>
#
#
import sys
PY = int(sys.version.split('.')[0])
"""
This module provides a SafeStr class.
@see: SafeStr
"""
class SafeStr(str):
"""
String class that has a modified __add__ method so that ascii strings,
binary data represented as a byte string and unicode objects can be
safely appended to it (not causing traceback). BINARY DATA IS OMITTED.
"""
def __add__(self, other):
if PY > 2:
return SafeStr(str.__add__(self, str(other)))
if not (isinstance(other, str) or isinstance(other, unicode)): # pylint: disable=undefined-variable
if hasattr(other, "__str__"):
other = other.__str__()
else:
other = "OMITTED OBJECT WITHOUT __str__ METHOD"
if isinstance(other, unicode): # pylint: disable=undefined-variable
ret = SafeStr(str.__add__(self, other.encode("utf-8")))
else:
try:
# try to decode which doesn't cause traceback for utf-8 encoded
# non-ascii string and ascii string
other.decode("utf-8")
ret = SafeStr(str.__add__(self, other))
except UnicodeDecodeError:
# binary data, get the representation used by Python for
# non-ascii bytes
# hex(255) returns "0xff", we want "\xff"
other_hexa = (hex(ord(char)) for char in other)
other_backslashed = (hex_num.replace("0x", "\\x")
for hex_num in other_hexa)
other_repr = "".join(other_backslashed)
ret = SafeStr(str.__add__(self, other_repr))
return ret
@@ -0,0 +1,181 @@
# Copyright (C) 2009 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Chris Lumens <clumens@redhat.com>
#
class AbstractIntf(object):
"""The base class for Interfaces. This is an abstract class. It
provides methods that create and run various kinds of windows.
We provide a basic level of graphical and text oriented interfaces
here, but it should be possible for things like anaconda to supply
their own much more complicated ones with little effort.
"""
def __init__(self, *args, **kwargs):
"""Create a new AbstractInterface instance. This method need not
be overridden by subclasses.
"""
pass
def enableNetwork(self, *args, **kwargs):
"""Display and run a dialog that brings up the network, if it is
currently disabled. This method must be provided by all subclasses,
and should return False if unsuccessful.
"""
raise NotImplementedError
def exitWindow(self, title, message, *args, **kwargs):
"""Display and run a dialog that displays a message with the proper
buttons and images for quitting afterwards. This method must be
provided by all subclasses.
title -- The window's title.
message -- The window's text.
"""
raise NotImplementedError
def mainExceptionWindow(self, text, exnFile, *args, **kwargs):
"""Create and return an instance of the initial execption handling
dialog. This method must be provided by all subclasses.
text -- The short traceback (usually, just the stack trace).
exnFile -- A file containing the output of ExceptionDump.write().
"""
raise NotImplementedError
def messageWindow(self, title, message, *args, **kwargs):
"""Display a generic informational message. This can be an error,
status update, and so forth. It must be provided by all subclasses.
title -- The window's title.
message -- The window's text.
"""
raise NotImplementedError
def saveExceptionWindow(self, exnFile, *args, **kwargs):
"""Create and return an instance of the exception saving dialog. This
method must be provided by all subclasses.
"""
raise NotImplementedError
class AbstractMainExceptionWindow(object):
"""This abstract class describes the basic level of support required of
any interface that supplies a main exception window. The main exception
window is the initial dialog displayed by the exception handler that
provides the debug/save/quit options.
"""
def __init__(self, shortTraceback=None, longTraceback=None, *args, **kwargs):
"""Create a new MainExceptionWindow instance. This must handle
creating the dialog and populating it with widgets, but must not
run the dialog. A self.dialog attribute should be created that
refers to the dialog itself.
shortTraceback -- The short traceback (usually, just the stack
trace).
longTraceback -- The long traceback (full traceback and the main
object dump).
"""
pass
def destroy(self, *args, **kwargs):
"""Destroy the current dialog. This method must be provided by all
subclasses.
"""
raise NotImplementedError
def run(self, *args, **kwargs):
"""Run the window and set a return value. This method does everything
after the interface has been set up, but does not destroy it. This
method must be provided by all subclasses.
"""
raise NotImplementedError
class AbstractExitWindow(object):
"""This abstract class describes the basic level of support required of
a basic message dialog that should be displayed before the program
exits. All interfaces must provide this class.
"""
def __init__(self, title, text, *args, **kwargs):
"""Create a new ExitWindow instance. This must handle creating the
dialog and putting the text into it, but must not run the dialog.
This method must be provided by all subclasses.
title -- The window's title.
text -- The window's text.
"""
pass
def destroy(self, *args, **kwargs):
"""Destroy the current dialog. This method must be provided by all
subclasses.
"""
raise NotImplementedError
def run(self, *args, **kwargs):
"""Run the window and set a return value. This method does everything
after the interface has been set up but does not destroy it. This
method must be provided by all subclasses.
"""
raise NotImplementedError
class AbstractMessageWindow(object):
"""This abstract class describes the basic level of support required of
a basic message dialog. All interfaces must provide this class.
"""
def __init__(self, title, text, *args, **kwargs):
"""Create a new MessageWindow instance. This must handle creating the
dialog and putting the text into it, but must not run the dialog.
This method must be provided by all subclasses.
title -- The window's title.
text -- The window's text.
"""
pass
def destroy(self, *args, **kwargs):
"""Destroy the current dialog. This method must be provided by all
subclasses.
"""
raise NotImplementedError
def run(self, *args, **kwargs):
"""Run the window and set a return value. This method does everything
after the interface has been set up but does not destroy it. This
method must be provided by all subclasses.
"""
raise NotImplementedError
class AbstractSaveExceptionWindow(object):
"""This abstract class describes the basic level of support required of
any interface that supplies an exception saving window. The exception
saving window is the dialog displayed if the user clicks "save" on the
initial dialog, and presents multiple options for how the traceback
should be saved.
"""
def __init__(self, *args, **kwargs):
"""Create a new SaveExceptionWindow instance. This must handle
creating the dialog and populating it with widgets, but must not
run the dialog. A self.dialog attribute should be created that
refers to the dialog itself.
"""
pass
def run(self, *args, **kwargs):
"""Run the window and set a return value. This method does everything
after the interface has been set up but does not destroy it. This
method must be provided by all subclasses.
"""
raise NotImplementedError
@@ -0,0 +1,177 @@
# Copyright (C) 2009 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Chris Lumens <clumens@redhat.com>
#
from meh import MAIN_RESPONSE_DEBUG, MAIN_RESPONSE_NONE, MAIN_RESPONSE_QUIT, MAIN_RESPONSE_SAVE
from meh.ui import AbstractIntf, AbstractSaveExceptionWindow, AbstractMainExceptionWindow, AbstractMessageWindow
import os
import sys
import report
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
import gettext
_ = lambda x: gettext.ldgettext("python-meh", x)
def find_glade_file(file):
path = os.environ.get("GLADEPATH", "./:ui/:/tmp/updates/:/tmp/updates/ui/:/usr/share/python-meh/")
for d in path.split(":"):
fn = d + file
if os.access(fn, os.R_OK):
return fn
raise RuntimeError("Unable to find glade file %s" % file)
class GraphicalIntf(AbstractIntf):
def __init__(self, *args, **kwargs):
AbstractIntf.__init__(self, *args, **kwargs)
def enableNetwork(self, *args, **kwargs):
"""Should be provided by the inheriting class."""
return False
def exitWindow(self, title, message, *args, **kwargs):
win = ExitWindow(title, message, *args, **kwargs)
win.run()
win.destroy()
def mainExceptionWindow(self, text, exnFile, *args, **kwargs):
win = MainExceptionWindow(text, exnFile, *args, **kwargs)
return win
def messageWindow(self, title, message, *args, **kwargs):
win = MessageWindow(title, message, *args, **kwargs)
win.run()
win.destroy()
def saveExceptionWindow(self, signature, *args, **kwargs):
win = SaveExceptionWindow(signature)
win.run()
class SaveExceptionWindow(AbstractSaveExceptionWindow):
def __init__(self, signature, *args, **kwargs):
AbstractSaveExceptionWindow.__init__(self, signature, *args, **kwargs)
self.signature = signature
def run(self, *args, **kwargs):
# Don't need to check the return value of report since it will
# handle all the UI reporting for us.
report.report_problem_in_memory(self.signature, report.LIBREPORT_WAIT)
class MainExceptionWindow(AbstractMainExceptionWindow):
def __init__(self, shortTraceback=None, longTraceback=None, *args, **kwargs):
AbstractMainExceptionWindow.__init__(self, shortTraceback, longTraceback,
*args, **kwargs)
builder = Gtk.Builder()
builder.set_translation_domain("python-meh")
glade_file = find_glade_file("exception-dialog.glade")
builder.add_from_file(glade_file)
builder.connect_signals(self)
self._main_window = builder.get_object("exceptionWindow")
# this makes sure the Meh window remains active
# even if the exception has been triggered in a dialog
window_group = Gtk.WindowGroup()
window_group.add_window(self._main_window)
self._traceback_buffer = builder.get_object("tracebackBuffer")
self._traceback_buffer.set_text(longTraceback)
self._response = MAIN_RESPONSE_QUIT
self._debug_button = builder.get_object("debugButton")
allowDebug = kwargs.get("allowDebug", sys.stdout.isatty)
if allowDebug and not allowDebug():
self._debug_button.set_sensitive(False)
@property
def main_window(self):
return self._main_window
def destroy(self, *args, **kwargs):
self._main_window.destroy()
def run(self, *args, **kwargs):
self._main_window.show_all()
# keep our window above dialogs
self._main_window.set_modal(True)
self._main_window.set_keep_above(True)
Gtk.main()
self.destroy()
return self._response
def on_report_clicked(self, button):
# hide our window to not interfere with libreport window
self._main_window.hide()
self._response = MAIN_RESPONSE_SAVE
Gtk.main_quit()
def on_quit_clicked(self, button):
self._response = MAIN_RESPONSE_QUIT
Gtk.main_quit()
def on_debug_clicked(self, button):
self._response = MAIN_RESPONSE_DEBUG
Gtk.main_quit()
def on_expander_activated(self, expander, *args):
if not expander.get_expanded():
self._main_window.resize(600, 400)
else:
#resize the window back to the default size when expander is
#minimized
self._main_window.resize(600, 1)
def on_main_window_deleted(self, *args):
self._response = MAIN_RESPONSE_NONE
self.destroy()
Gtk.main_quit()
class MessageWindow(AbstractMessageWindow):
def __init__(self, title, text, *args, **kwargs):
AbstractMessageWindow.__init__(self, title, text, *args, **kwargs)
self.rc = 0
self.dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.OK,
type=Gtk.MessageType.INFO,
message_format=text)
self.dialog.set_title(title)
self.dialog.set_position(Gtk.WindowPosition.CENTER)
def destroy(self, *args, **kwargs):
self.dialog.destroy()
def run(self, *args, **kwargs):
self.dialog.show_all()
self.rc = self.dialog.run()
self.dialog.destroy()
class ExitWindow(MessageWindow):
def __init__(self, title, text, *args, **kwargs): # pylint: disable=super-init-not-called
self.dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.NONE,
type=Gtk.MessageType.INFO,
message_format=text)
self.dialog.set_title(title)
self.dialog.add_button(_("_Exit"), 0)
self.dialog.set_position(Gtk.WindowPosition.CENTER)
@@ -0,0 +1,225 @@
# Copyright (C) 2009, 2013 Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Chris Lumens <clumens@redhat.com>
# Vratislav Podzimek <vpodzime@redhat.com>
#
from __future__ import print_function
from meh import MAIN_RESPONSE_DEBUG, MAIN_RESPONSE_SAVE, MAIN_RESPONSE_SHELL, MAIN_RESPONSE_QUIT
from meh.ui import AbstractIntf, AbstractSaveExceptionWindow, AbstractMainExceptionWindow, AbstractMessageWindow
import report
import report.io.TextIO
from report import LIBREPORT_WAIT, LIBREPORT_RUN_CLI
import os
import sys
if sys.version_info.major == 3:
raw_input_fn = input
else:
raw_input_fn = raw_input # pylint: disable=undefined-variable
import gettext
_ = lambda x: gettext.ldgettext("python-meh", x)
class IOHandler(object):
"""
Class that provides methods for input and output. Its instance is expected
to be passed to objects performing some I/O operations.
"""
def __init__(self, in_func=raw_input_fn, out_func=print): # pylint: disable=used-before-assignment
"""
Constructor for the IOhandler class. Arguments can be used to override
default I/O functions with the custom ones.
:param in_func: input function similar to standard raw_input
:type in_func: str -> str
:param out_func: output function similar to standard print
:type out_func: str -> None
"""
self.in_func = in_func
self.out_func = out_func
def print(self, msg=""):
self.out_func(msg)
def raw_input(self, prompt):
return self.in_func(prompt)
class TextWindow(object):
"""Helper class providing some common methods needed by all text windows."""
def __init__(self, title, *args, **kwargs):
self._io = kwargs.get("io_handler", IOHandler())
self._title = title
# short (one-char) answer meaning "yes"
self._yes_answer = _("y")
# short (one-char) answer meaning "no"
self._no_answer = _("n")
@property
def _usable_width(self):
return os.environ.get("COLUMNS", 80) - 1
def _print_rule(self):
rule = self._usable_width * "="
self._io.print(rule)
def print_header(self):
self._io.print()
self._print_rule()
self._io.print(self._title)
self._print_rule()
def destroy(self):
self._print_rule()
self._print_rule()
class TextIntf(AbstractIntf):
def __init__(self, *args, **kwargs):
AbstractIntf.__init__(self, *args, **kwargs)
self.screen = kwargs.get("screen", None)
self._io = kwargs.get("io_handler", IOHandler())
def set_io_handler(self, handler):
"""
Set different IO handler.
:type handler: an instance of the IOHandler class
"""
self._io = handler
def enableNetwork(self, *args, **kwargs):
"""Should be provided by the inheriting class."""
return False
def exitWindow(self, title, message, *args, **kwargs):
kwargs["io_handler"] = self._io
win = ExitWindow(title, message, *args, **kwargs)
win.run()
win.destroy()
def mainExceptionWindow(self, text, exnFile, *args, **kwargs):
kwargs["io_handler"] = self._io
win = MainExceptionWindow(text, exnFile, *args, **kwargs)
return win
def messageWindow(self, title, message, *args, **kwargs):
kwargs["io_handler"] = self._io
win = MessageWindow(title, message, *args, **kwargs)
win.run()
win.destroy()
def saveExceptionWindow(self, signature, *args, **kwargs):
kwargs["io_handler"] = self._io
win = SaveExceptionWindow(signature, *args, **kwargs)
win.run()
win.destroy()
class SaveExceptionWindow(TextWindow, AbstractSaveExceptionWindow):
def __init__(self, signature, *args, **kwargs):
AbstractSaveExceptionWindow.__init__(self, signature,
*args, **kwargs)
TextWindow.__init__(self, _("Save exception"), *args, **kwargs)
self.signature = signature
def run(self, *args, **kwargs):
# Don't need to check the return value of report since it will
# handle all the UI reporting for us.
report.report_problem_in_memory(self.signature,
LIBREPORT_WAIT|LIBREPORT_RUN_CLI)
class MainExceptionWindow(TextWindow, AbstractMainExceptionWindow):
def __init__(self, shortTraceback=None, longTraceback=None, *args, **kwargs):
AbstractMainExceptionWindow.__init__(self, shortTraceback, longTraceback,
*args, **kwargs)
TextWindow.__init__(self, _("An unknown error has occurred"),
*args, **kwargs)
self._short_traceback = shortTraceback
self._menu_items = [(_("Report Bug"), MAIN_RESPONSE_SAVE),
(_("Run shell"), MAIN_RESPONSE_SHELL),
(_("Quit"), MAIN_RESPONSE_QUIT)]
allowDebug = kwargs.get("allowDebug", sys.stdout.isatty)
if allowDebug and allowDebug():
self._menu_items.insert(1, (_("Debug"), MAIN_RESPONSE_DEBUG))
def run(self, *args, **kwargs):
self.print_header()
self._io.print(self._short_traceback)
self._io.print(_("What do you want to do now?"))
for (idx, item) in enumerate(self._menu_items):
self._io.print("%d) %s" % (idx + 1, item[0]))
ret = -1
num_menu_items = len(self._menu_items)
self._io.print()
while not (0 < ret <= num_menu_items):
ret = self._io.raw_input(_("Please make your choice from above: "))
try:
ret = int(ret)
except ValueError:
ret = -1
return self._menu_items[ret - 1][1]
class MessageWindow(TextWindow, AbstractMessageWindow):
def __init__(self, title, text, *args, **kwargs):
AbstractMessageWindow.__init__(self, title, text, *args, **kwargs)
TextWindow.__init__(self, title, *args, **kwargs)
self._text = text
def run(self, *args, **kwargs):
self.print_header()
self._io.print(self._text)
self._io.print()
self._io.raw_input(_("Hit ENTER to continue"))
class ExitWindow(MessageWindow):
def __init__(self, title, text, *args, **kwargs):
MessageWindow.__init__(self, title, text, *args, **kwargs)
def run(self, *args, **kwargs):
self.print_header()
self._io.print(self._text)
self._io.print()
# self._no_answer may be non-ascii string (simple .upper() doesn't work)
no_answer_upper = self._no_answer.decode("utf-8").upper().encode("utf-8")
answer = self._io.raw_input(_(
"Are you sure you want to exit? [%(yes)s/%(no)s]") %
{ "yes": self._yes_answer,
"no": no_answer_upper })
# no answer means accepting the default (self._no_answer) and the answer
# is case insensitive (and may be non-ascii)
lower_answer = answer.decode("utf-8").lower().encode("utf-8")
answer = lower_answer or self._no_answer
if answer in (self._yes_answer, self._no_answer):
return answer == self._yes_answer
else:
self.run(*args, **kwargs)
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,76 @@
#
# Makefile for the PO files (translation) catalog
#
# $Id$
TOP = ../..
# What is this package?
NLSPACKAGE = python-meh
POTFILE = $(NLSPACKAGE).pot
INSTALL = /usr/bin/install -c
INSTALL_DATA = $(INSTALL) -m 644
INSTALL_DIR = /usr/bin/install -d
# destination directory
INSTALL_NLS_DIR = $(RPM_BUILD_ROOT)/usr/share/locale
# PO catalog handling
MSGMERGE = msgmerge -v
XGETTEXT = xgettext --default-domain=$(NLSPACKAGE) \
--add-comments
MSGFMT = msgfmt --statistics --verbose
# What do we need to do
POFILES = $(wildcard *.po)
MOFILES = $(patsubst %.po,%.mo,$(POFILES))
PYSRC = $(wildcard ../meh/*.py ../meh/ui/*.py)
GLADEFILES = $(wildcard ../ui/*.glade)
all:: update-po $(MOFILES)
potfile: $(PYSRC) glade-po
$(XGETTEXT) -L Python --keyword=_ --keyword=N_ $(PYSRC) tmp/*.h
@if cmp -s $(NLSPACKAGE).po $(POTFILE); then \
rm -f $(NLSPACKAGE).po; \
else \
mv -f $(NLSPACKAGE).po $(POTFILE); \
fi; \
glade-po: $(GLADEFILES)
rm -rf tmp/
for f in $(GLADEFILES); do \
intltool-extract --type=gettext/glade -l $$f ;\
done
update-po: Makefile potfile refresh-po
refresh-po: Makefile
for cat in $(POFILES); do \
lang=`basename $$cat .po`; \
if $(MSGMERGE) $$lang.po $(POTFILE) > $$lang.pot ; then \
mv -f $$lang.pot $$lang.po ; \
echo "$(MSGMERGE) of $$lang succeeded" ; \
else \
echo "$(MSGMERGE) of $$lang failed" ; \
rm -f $$lang.pot ; \
fi \
done
clean:
@rm -fv *mo *~ .depend
@rm -rf tmp
install: $(MOFILES)
@for n in $(MOFILES); do \
l=`basename $$n .mo`; \
$(INSTALL_DIR) $(INSTALL_NLS_DIR)/$$l/LC_MESSAGES; \
$(INSTALL_DATA) --verbose $$n $(INSTALL_NLS_DIR)/$$l/LC_MESSAGES/$(NLSPACKAGE).mo; \
done
%.mo: %.po
$(MSGFMT) -o $@ $<
.PHONY: missing depend
@@ -0,0 +1,116 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-10-05 14:50+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../meh/handler.py:195
msgid ""
"Use 'continue' command to quit the debugger and get back to the main menu"
msgstr ""
#: ../meh/handler.py:211
msgid "Exit the shell to get back to the main menu"
msgstr ""
#. short (one-char) answer meaning "yes"
#: ../meh/ui/text.py:75
msgid "y"
msgstr ""
#. short (one-char) answer meaning "no"
#: ../meh/ui/text.py:78
msgid "n"
msgstr ""
#: ../meh/ui/text.py:145
msgid "Save exception"
msgstr ""
#: ../meh/ui/text.py:158 tmp/exception-dialog.glade.h:1
msgid "An unknown error has occurred"
msgstr ""
#: ../meh/ui/text.py:161
msgid "Report Bug"
msgstr ""
#: ../meh/ui/text.py:162
msgid "Run shell"
msgstr ""
#: ../meh/ui/text.py:163
msgid "Quit"
msgstr ""
#: ../meh/ui/text.py:168
msgid "Debug"
msgstr ""
#: ../meh/ui/text.py:173
msgid "What do you want to do now?"
msgstr ""
#: ../meh/ui/text.py:181
msgid "Please make your choice from above: "
msgstr ""
#: ../meh/ui/text.py:199
msgid "Hit ENTER to continue"
msgstr ""
#: ../meh/ui/text.py:213
#, python-format
msgid "Are you sure you want to exit? [%(yes)s/%(no)s]"
msgstr ""
#: ../meh/ui/gui.py:176
msgid "_Exit"
msgstr ""
#: tmp/exception-dialog.glade.h:2
msgid ""
"This program has encountered an unknown error. You may\n"
"report the bug below or quit the program."
msgstr ""
#: tmp/exception-dialog.glade.h:4
msgid "_Report Bug"
msgstr ""
#: tmp/exception-dialog.glade.h:5
msgid "_Quit"
msgstr ""
#: tmp/exception-dialog.glade.h:6
msgid "The output below may help determine the cause of the error:"
msgstr ""
#: tmp/exception-dialog.glade.h:7
msgid "'Debug' may take you to tty1."
msgstr ""
#: tmp/exception-dialog.glade.h:8
msgid "_Debug"
msgstr ""
#: tmp/exception-dialog.glade.h:9
msgid "More _info..."
msgstr ""
#: tmp/exception-dialog.glade.h:10
msgid "No additional info available."
msgstr ""
@@ -0,0 +1,383 @@
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
%global with_python3 1
%define libreportver 2.0.18-1
Summary: A python library for handling exceptions
Name: python-meh
Url: https://github.com/rhinstaller/python-meh
Version: 0.43
Release: 1%{?dist}
# This is a Red Hat maintained package which is specific to
# our distribution. Thus the source is only available from
# within this srpm.
# This tarball was created from upstream git:
# git clone https://github.com/rhinstaller/python-meh
# cd python-meh && make archive
Source0: https://github.com/rhinstaller/python-meh/archive/%{name}-%{version}.tar.gz
License: GPLv2+
Group: System Environment/Libraries
BuildArch: noarch
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: python-devel
BuildRequires: gettext
BuildRequires: python-setuptools
BuildRequires: intltool
BuildRequires: dbus-python
BuildRequires: libreport-gtk >= %{libreportver}
BuildRequires: libreport-cli >= %{libreportver}
BuildRequires: libreport-python >= %{libreportver}
BuildRequires: python-six
%if 0%{with_python3}
BuildRequires: python3-devel
BuildRequires: python3-setuptools
BuildRequires: python3-dbus
BuildRequires: libreport-python3 >= %{libreportver}
BuildRequires: python3-six
BuildRequires: python3-pocketlint
%endif
Requires: python
Requires: dbus-python
Requires: rpm-python
Requires: libreport-cli >= %{libreportver}
Requires: libreport-python >= %{libreportver}
Requires: python-six
%description
The python-meh package is a python library for handling, saving, and reporting
exceptions.
%package gui
Summary: Graphical user interface for the python-meh library
Requires: python-meh = %{version}-%{release}
Requires: pygobject3
Requires: gtk3
Requires: libreport-gtk >= %{libreportver}
%description gui
The python-meh-gui package provides a GUI for the python-meh library.
%if 0%{with_python3}
%package -n python3-meh
Summary: A python 3 library for handling exceptions
Requires: python3
Requires: python3-dbus
Requires: rpm-python3
Requires: libreport-cli >= %{libreportver}
Requires: libreport-python3 >= %{libreportver}
Requires: python3-six
%description -n python3-meh
The python3-meh package is a python 3 library for handling, saving, and reporting
exceptions.
%package -n python3-meh-gui
Summary: Graphical user interface for the python3-meh library
Requires: python3-meh = %{version}-%{release}
Requires: python3-gobject, gtk3
Requires: libreport-gtk >= %{libreportver}
%description -n python3-meh-gui
The python3-meh-gui package provides a GUI for the python3-meh library.
%endif
%prep
%setup -q
%if 0%{?with_python3}
rm -rf %{py3dir}
cp -a . %{py3dir}
%endif # with_python3
%build
make
%if 0%{?with_python3}
pushd %{py3dir}
make PYTHON=%{__python3}
popd
%endif
%check
make test
%if 0%{?with_python3}
pushd %{py3dir}
# Needs UTF-8 locale
LANG=en_US.UTF-8 make PYTHON=%{__python3} test
popd
%endif
%install
rm -rf %{buildroot}
make DESTDIR=%{buildroot} install
%if 0%{?with_python3}
pushd %{py3dir}
make PYTHON=%{__python3} DESTDIR=%{buildroot} install
popd
%endif
%find_lang %{name}
%clean
rm -rf %{buildroot}
%files -f %{name}.lang
%defattr(-,root,root,-)
%doc ChangeLog COPYING
%{python_sitelib}/*
%exclude %{python_sitelib}/meh/ui/gui.py*
%files gui
%{python_sitelib}/meh/ui/gui.py*
%{_datadir}/python-meh
%files -n python3-meh
%doc ChangeLog COPYING
%{python3_sitelib}/*
%exclude %{python3_sitelib}/meh/ui/gui.py*
%files -n python3-meh-gui
%{python3_sitelib}/meh/ui/gui.py*
%{_datadir}/python-meh
%changelog
* Mon Oct 05 2015 Martin Kolman <mkolman@redhat.com> - 0.43-1
- Set translations domain for glade files (vtrefny)
* Tue Sep 29 2015 Martin Kolman <mkolman@redhat.com> - 0.42-1
- Add tests and ChangeLog to MANIFEST.in (vtrefny)
- Fix clicking on buttons on error pop-up doesn't work (#1250942) (vtrefny)
- Add Makefile to MANIFEST.in (vtrefny)
- Add archive target to Makefile (vtrefny)
- Add .gitignore (vtrefny)
* Wed Sep 16 2015 Martin Kolman <mkolman@redhat.com> - 0.41-1
- Make sure the Meh window has focus even if a dialog is displayed (mkolman)
- Run pylint as part of "make ci". (clumens)
- Add a new makefile target that does everything needed for jenkins. (clumens)
- Fix the other pylint-discovered errors. (clumens)
- Use isinstance instead of type. (clumens)
- Fix all the places we're redefining something. (clumens)
- Don't use [] as the default argument in a method. (clumens)
- Take care of exception-related pylint messages. (clumens)
- Get rid of all the unused variable warnings. (clumens)
- Get rid of all unused and wildcard imports. (clumens)
- Add pocketlint support to python-meh. (clumens)
- Use gi.require_version for Gtk import (vtrefny)
* Tue Apr 28 2015 Martin Kolman <mkolman@redhat.com> - 0.40-1
- Make sure the date in RPM changelog is always in English (mkolman)
- Update upstream URL (mkolman)
* Wed Apr 01 2015 Martin Kolman <mkolman@redhat.com> - 0.39-1
- Handle LANG=C (mkolman)
* Tue Mar 31 2015 Martin Kolman <mkolman@redhat.com> - 0.38-1
- Switch to Zanata for translations (mkolman)
- Run both Python 2 and Python 3 tests for "make test" (mkolman)
- Python 3 compatibility fixes for the Unicode test (mkolman)
- Make it possible to specify file open mode (mkolman)
- Add missing six dependency (mkolman)
- Fix date in changelog (mkolman)
* Fri Mar 27 2015 Martin Kolman <mkolman@redhat.com> - 0.37-1
- Fix dumping file attachments in Python 3 (vtrefny)
- meh has not required openssh-clients since 2009 (awilliam)
* Thu Mar 05 2015 Martin Kolman <mkolman@redhat.com> - 0.36-1
- Package's epoch returned by RPM is an integer (#1199263) (vpodzime)
* Tue Feb 17 2015 Martin Kolman <mkolman@redhat.com> - 0.35-1
- Add option to remove the 'Debug' option/button for mainExceptionWindow (vtrefny)
* Tue Dec 09 2014 Vratislav Podzimek <vpodzime@redhat.com> - 0.34-1
- Encode str/unicode object before hashing it (vpodzime)
- Use dict.items() instead of dict.iteritems() (vpodzime)
- Add one more flag to test (for Python3) (vpodzime)
- Make sure we work with strings when we think we do (vpodzime)
* Fri Dec 05 2014 Vratislav Podzimek <vpodzime@redhat.com> - 0.33-1
- Make sure fresh translations are always fetched (vpodzime)
- Add pieces needed to build the python3- subpackage (#985294) (mhroncok)
- Remove yum from requires and fix setuptools requires (mhroncok)
- raw_input is replaced by input in Python3 (vpodzime)
- Raise exception in a Python3 compatible way (mhroncok)
- Remove the --disable-overwrite parameter for the Transifex client (mkolman)
- Use /usr/bin/python2 in scripts (mkolman)
- Add example code using python-meh (vpodzime)
* Mon Apr 28 2014 Martin Kolman <mkolman@redhat.com> - 0.32-1
- Translation update
* Fri Mar 21 2014 Martin Kolman <mkolman@redhat.com> - 0.31-1
- Translation update
* Wed Dec 18 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.30-1
- Exclude compiled versions of gui.py from the non-gui package (dshea)
- Skip callbacks providing no information (vpodzime)
* Wed Nov 20 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.29-1
- Split GUI out into a separate package (vpodzime)
- Create archives in one Makefile target and reuse it (vpodzime)
* Tue Nov 05 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.28-1
- Introduce support for Python 3 while keeping Python 2 working (miro)
- Sync spec with downstream (vpodzime)
* Wed Oct 09 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.27-1
- Use join method instead of the joinfields function
- Translate the hints on how to quit debugger and shell
- Add a way to run shell when exception appears
- Get rid of constants that are no longer used anywhere
- Filter local variables in a nicer way and fix docstring
* Sun Aug 04 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.26-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild
* Tue Jul 23 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.26-1
- Output binary data correctly as hexa strings (#986515) (vpodzime)
- Add newline before dumping callbacks' outputs (vpodzime)
* Tue Jun 18 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.25-1
- Add and use the safe_string module and the SafeStr class (vpodzime)
- Give translators hint about the cryptic strings (vpodzime)
* Thu May 02 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.24-1
- Epoch of the package from RPM db can be None (#957789) (vpodzime)
* Wed Apr 17 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.23-1
- Use Sphinx syntax for docstrings (vpodzime)
- Allow a change of the I/O functions (vpodzime)
- Reword the 'Debug' button warning (#948256) (vpodzime)
* Thu Apr 04 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.22-1
- Some more stuff for ABRT/libreport (#929181) (vpodzime)
- Tell ABRT we are reporting a Python excetion (vpodzime)
- Use named tuples instead of our magic tuples (vpodzime)
* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.21-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
* Mon Jan 21 2013 Vratislav Podzimek <vpodzime@redhat.com> - 0.21-1
- New purely textual text interface (vpodzime)
- Add dumb enableNetwork implementation to GraphicalIntf (vpodzime)
- Destroy main window after running it (vpodzime)
- Get rid of the accountManager and use new libreport API (vpodzime)
- Add a way to override previously registered callback (vpodzime)
- Allow callbacks marked as attachment only (vpodzime)
- Add support for callbacks providing additional data (vpodzime)
* Tue Dec 11 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.20-1
- Handle non-ascii dict keys and values correctly (#883641) (vpodzime)
* Wed Nov 14 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.19-1
- Add test for handling unicode strings and files (vpodzime)
- Read files as UTF-8 and ignore errors (#874250) (vpodzime)
- Add %check section to the spec file (vpodzime)
- Fix tests (vpodzime)
* Thu Oct 25 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.18-1
- Handle tracebacks with no stack (#866441) (vpodzime)
- Parse component name correctly (#866526) (vpodzime)
- Spelling corrections (#865993) (vpodzime)
* Tue Oct 09 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.17-1
- Handle unicode strings correctly (#854959) (vpodzime)
* Tue Sep 11 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.16-1
- Do not overwrite process information with files having the same basename (vpodzime)
- Encode dump as utf-8 before writing to file (#854959) (vpodzime)
* Mon Aug 20 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.15-1
- Add main_window property to the MainExceptionWindow (vpodzime)
- Don't try to dump objects without __dict__ (vpodzime)
- Change require from rpm to rpm-python (vpodzime)
* Fri Aug 03 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.14-1
- Use just a basename of the attached file as the item name (vpodzime)
- Set the type hint for the mainExceptionWindow to Dialog (vpodzime)
- Store and then write out the string representation of the traceback and object dump (vpodzime)
* Fri Jul 27 2012 Vratislav Podzimek <vpodzime@redhat.com> - 0.13-1
- Add files specified in the Config object as attachments to bugreports (vpodzime)
- Display hint how to quit the debugger (vpodzime)
- Do not kill the process when 'continue' is used in pdb (vpodzime)
- Port to Gtk3 and the new design (vpodzime)
- Remove the rc attribute and getrc methods (vpodzime)
- Fix 'all' and 'install' Makefile targets (vpodzime)
- Migrate l10n to Transifex (vpodzime)
* Sat Jul 21 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.12-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild
* Tue Dec 20 2011 Vratislav Podzimek <vpodzime@redhat.com> 0.12
- Use new libreport API to get more information to bugzilla (vpodzime).
- Adapt to the new API of libreport (vpodzime).
- Move "import rpm" to where it's needed to avoid nameserver problems (clumens).
Resolves: rhbz#749330
- Change dependency to libreport-* (mtoman).
Resolves: rhbz#730924
- Add abrt-like information to bug reports (vpodzime).
Resolves: rhbz#728871
- Propagate the screen attr when using text mode (jmoskovc).
* Tue Feb 08 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.11-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
* Tue Jan 25 2011 Chris Lumens <clumens@redhat.com> - 0.11-1
- Update the spec file URL to something valid (#670601). (clumens)
- Don't use _D for Debug, since that's already used by the expander (#640929). (clumens)
- Translation updates.
* Tue Jun 22 2010 Chris Lumens <clumens@redhat.com> 0.10-1
- Treat classes like simple types, too. (clumens)
* Thu Jun 10 2010 Chris Lumens <clumens@redhat.com> - 0.9-1
- Remove the requirement on python-bugzilla (#602794). (clumens)
- Rename ba.po -> bs.po (#583055). (clumens)
- Translation updates.
* Thu Mar 04 2010 Chris Lumens <clumens@redhat.com> - 0.8-1
- And add a requirement for report as well. (clumens)
- filer.py is now completely taken over by meh. (clumens)
- Everything from savers.py has moved into report. (clumens)
- Remove unused UI code now that report handles all this for me. (clumens)
- Switch ExceptionHandler to use report (#562656). (clumens)
- Don't allow an exception when writing out an attribute stop the whole dump. (clumens)
- Credit where credit is due. (clumens)
* Tue Nov 03 2009 Chris Lumens <clumens@redhat.com> - 0.7-1
- Add a test case framework.
- Move src -> meh for ease of test case writing.
- Another attempt at making the attrSkipList work (#532612, #532737).
* Thu Oct 08 2009 Chris Lumens <clumens@redhat.com> - 0.6-1
- Make idSkipList work again.
- Support dumping objects derived from Python's object.
- Use the right method to set text on a snack.Entry (#526884).
* Tue Sep 29 2009 Chris Lumens <clumens@redhat.com> - 0.5-1
- Always compare version numbers as strings (#526188).
* Fri Sep 25 2009 Chris Lumens <clumens@redhat.com> - 0.4-1
- Add a default description to bug reports.
- Handle the user pressing Escape by continuing to show the dialog.
- Lots more translation updates.
* Thu Sep 10 2009 Chris Lumens <clumens@redhat.com> - 0.3-1
- Pull in lots of new translations (#522410).
* Wed Aug 19 2009 Chris Lumens <clumens@redhat.com> - 0.2-1
- Add a title to the main exception dialog so it looks right in anaconda.
- Don't include an extra '/' in the displayed bug URL (#517515).
- Now that there's .po files, package them.
- Use the new exception icon (#517164).
* Tue Jul 28 2009 Chris Lumens <clumens@redhat.com> - 0.1-1
- Initial package.
@@ -0,0 +1,10 @@
#!/usr/bin/python2
from distutils.core import setup
setup(name='python-meh', version='0.43',
description='Python module for handling exceptions',
author='Chris Lumens', author_email='clumens@redhat.com',
url='http://git.fedoraproject.org/git/?p=python-meh.git',
data_files = [('/usr/share/python-meh', ['ui/exception-dialog.glade'])],
packages=['meh', 'meh.ui'])
@@ -0,0 +1,25 @@
import unittest
from tests.baseclass import BaseTestCase
from meh import Config
class Example:
def __init__(self):
self.rootPassword = "blahblah"
self.dontSkipMe = 12345
class AttrSkipList_TestCase(BaseTestCase):
def runTest(self):
example = Example()
conf = Config(programName="test",
programVersion="47",
attrSkipList=[ "rootPassword" ])
dump = self.dump(conf, example)
self.assertIn("rootPassword: Skipped", dump)
self.assertNotIn("dontSkipMe: Skipped", dump)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,103 @@
import glob
import importlib
import os
import sys
import tempfile
import unittest
import six
from meh import ExceptionInfo
from meh.dump import ExceptionDump
class BaseTestCase(unittest.TestCase):
def dump(self, conf, obj):
from inspect import stack as _stack
stack = _stack()[1:]
dump = ExceptionDump(ExceptionInfo(None, None, stack), conf)
return dump.dump(obj)
def openFile(self, mode="w"):
(fd, path) = tempfile.mkstemp()
# only Python 3 has supports the "encoding" keyword argument
if six.PY2:
fo = os.fdopen(fd, mode)
else:
fo = os.fdopen(fd, mode, encoding="utf-8")
return (fo, path)
def tracebackContains(self, path, s):
f = open(path)
while True:
l = f.readline()
if l == "":
f.close()
return False
if l.find(s) != -1:
f.close()
return True
f.close()
return False
def loadModules(moduleDir, cls_pattern="_TestCase", skip_list=None):
'''taken from firstboot/loader.py'''
if not skip_list:
skip_list = ["__init__", "baseclass"]
# Guaruntee that __init__ is skipped
if skip_list.count("__init__") == 0:
skip_list.append("__init__")
tests = list()
# Make sure moduleDir is in the system path so imputil works.
if not moduleDir in sys.path:
sys.path.insert(0, moduleDir)
# Get a list of all *.py files in moduleDir
lst = map(lambda x: os.path.splitext(os.path.basename(x))[0],
glob.glob(moduleDir + "/*.py"))
# Inspect each .py file found
for module in lst:
if module in skip_list:
continue
# Attempt to load the found module.
try:
loaded = importlib.import_module(module)
except ImportError:
print("Error loading module %s." % module)
continue
# Find class names that match the supplied pattern (default: "_TestCase")
beforeCount = len(tests)
for obj in loaded.__dict__.keys():
if obj.endswith(cls_pattern):
tests.append(loaded.__dict__[obj])
afterCount = len(tests)
# Warn if no tests found
if beforeCount == afterCount:
print("Module %s does not contain any test cases; skipping." % module)
continue
return tests
# Run the tests
if __name__ == "__main__":
# Create a test suite
MehTestSuite = unittest.TestSuite()
# Find tests to add
tstList = loadModules(os.path.join(os.environ.get("PWD"), "tests/"))
for tst in tstList:
MehTestSuite.addTest(tst())
# Run tests
unittest.main(defaultTest="MehTestSuite")
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from tests.baseclass import BaseTestCase
from meh import Config, ConfigError
def callback1():
return "This was generated by the callback1"
def callback2():
return "This was generated by the callback2"
def callback3():
raise RuntimeError("Testing callback raising exception")
def callback4():
return "This was generated by the callback4"
def callback5():
return ""
class Callbacks_TestCase(BaseTestCase):
def runTest(self):
conf = Config(programName="CallbacksTest",
programVersion="1.0",
callbackDict={"callback1": (callback1, False),
"callback2": (callback2, False)})
# another way to register callback
conf.register_callback("callback3", callback2)
# callback with given item name already registered
with self.assertRaises(ConfigError):
conf.register_callback("callback3", callback3)
# should not raise exception
conf.register_callback("callback3", callback3, override=True)
conf.register_callback("callback4", callback4, attchmnt_only=True)
conf.register_callback("callback5", callback5, attchmnt_only=False)
# should not raise exception
dump = self.dump(conf, None)
self.assertIn("callback1:\nThis was generated by the callback1\n",
dump)
self.assertIn("callback2:\nThis was generated by the callback2\n",
dump)
self.assertIn("callback3: Caused error", dump)
# should not appear in the dump (attachment only)
self.assertNotIn("callback4", dump)
# should not appear in the dump (nothing provided)
self.assertNotIn("callback5", dump)
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
from tests.baseclass import BaseTestCase
from meh import Config
BINARY_DATA = b"\xff\x61\xfe\xdd"
BINARY_DATA2 = b"\xfe\x61\xff\xdd"
BINARY_DATA3 = b"\xfe\x62\xff\xdd"
class BinaryExample(object):
def __init__(self):
self.bin_data = BINARY_DATA
# crazy, but possible
self.dict = dict()
self.dict[BINARY_DATA2] = BINARY_DATA2
self.list = [BINARY_DATA3]
class HandleBinary_TestCase(BaseTestCase):
def runTest(self):
binary_example = BinaryExample()
conf = Config(programName="UnicodeTest",
programVersion="1.0")
# should not raise exception
dump = self.dump(conf, binary_example)
# should contain the attribute name and hexa representation of binary
# data ('\x61' == 'a' which shouldn't be translated)
self.assertTrue("bin_data: \\xff\\x61\\xfe\\xdd\n" in dump or
"bin_data: b'\\xffa\\xfe\\xdd'\n" in dump)
# should contain the binary-keyed dict
self.assertTrue("dict: {'\\xfe\\x61\\xff\\xdd': \\xfe\\x61\\xff\\xdd" in dump or
"dict: {b'\\xfea\\xff\\xdd': b'\\xfea\\xff\\xdd'" in dump)
# should contain the list with binary item(s)
self.assertTrue("list: [\\xfe\\x62\\xff\\xdd]" in dump or
"list: [b'\\xfeb\\xff\\xdd']" in dump)
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
import six
from tests.baseclass import BaseTestCase
from meh import Config
UNICODE_STR = u"řěšččšě"
UNICODE_LINE = u"řčšřščřčš\n"
ASCII_STR = "fsdkljfdsldfs"
ASCII_LINE = "fdsfsdakjfdsa\n"
class UnicodeExample(object):
def __init__(self):
self.ascii_str = ASCII_STR
self.unicode_str = UNICODE_STR
self.encoded_str = self.unicode_str.encode("utf-8")
self.unicode_dict = { u"úú" : u"áá" }
class HandleUnicode_TestCase(BaseTestCase):
def setUp(self):
# write UTF-8 and ASCII files for testing
if six.PY2:
(fobj, self.uni_file_path) = self.openFile()
else:
(fobj, self.uni_file_path) = self.openFile(mode="wt")
try:
fobj.write(UNICODE_LINE)
except UnicodeEncodeError:
fobj.write(UNICODE_LINE.encode("utf-8"))
fobj.close()
(fobj, self.ascii_file_path) = self.openFile()
fobj.write(ASCII_LINE)
fobj.close()
def runTest(self):
unicode_example = UnicodeExample()
conf = Config(programName="UnicodeTest",
programVersion="1.0",
fileList=[self.uni_file_path, self.ascii_file_path])
# should not raise exception
dump = self.dump(conf, unicode_example)
self.assertIn("_str: " + str(UNICODE_STR.encode("utf-8")), dump)
self.assertIn("encoded_str: " + str(UNICODE_STR.encode("utf-8")), dump)
if six.PY2:
self.assertIn(str(UNICODE_LINE.encode("utf-8")), dump)
else:
self.assertIn(UNICODE_LINE, dump)
self.assertIn("ascii_str: " + ASCII_STR, dump)
self.assertIn(ASCII_LINE.rstrip(), dump)
@@ -0,0 +1,19 @@
#!/usr/bin/python3
import sys
from pocketlint import PocketLintConfig, PocketLinter
class MehLintConfig(PocketLintConfig):
@property
def pylintPlugins(self):
retval = super(MehLintConfig, self).pylintPlugins
retval.remove("pocketlint.checkers.eintr")
retval.remove("pocketlint.checkers.markup")
return retval
if __name__ == "__main__":
conf = MehLintConfig()
linter = PocketLinter(conf)
rc = linter.run()
sys.exit(rc)
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from tests.baseclass import BaseTestCase
from meh.safe_string import SafeStr, PY
class TestClass(object):
def __str__(self):
return "string representation of TestClass instance"
class TestClass2():
pass
class SafeStr_TestCase(BaseTestCase):
def setUp(self):
self.safestr = SafeStr()
self.unistr = u"ááááá"
self.enc_unistr = self.unistr.encode("utf-8")
self.asciistr = "aaaa"
self.bindata = b'\xff\xff\xfe'
self.test_object = TestClass()
self.test_object2 = TestClass2()
def runTest(self):
self.safestr += self.asciistr
self.safestr += self.enc_unistr
self.safestr += self.unistr
self.safestr += self.bindata
self.safestr += self.test_object
self.safestr += self.test_object2
self.assertIn(self.asciistr, self.safestr)
if PY == 2:
self.assertIn("OMITTED OBJECT WITHOUT __str__ METHOD", self.safestr)
# should be included twice -- appended enc_unistr and unistr
self.assertIn(str(self.enc_unistr), self.safestr)
else:
self.assertIn("<safe_string_test.TestClass2 object at ", self.safestr)
self.assertIn(str(self.enc_unistr), self.safestr)
self.assertIn(self.unistr, self.safestr)
self.assertIn("\\xff\\xff\\xfe", self.safestr)
self.assertIn(str(self.test_object), self.safestr)
@@ -0,0 +1,248 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkWindow" id="exceptionWindow">
<property name="can_focus">False</property>
<property name="window_position">center-always</property>
<property name="default_width">600</property>
<property name="type_hint">dialog</property>
<property name="urgency_hint">True</property>
<signal name="delete-event" handler="on_main_window_deleted" swapped="no"/>
<child>
<object class="GtkBox" id="mainBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">center</property>
<property name="margin_left">20</property>
<property name="margin_top">30</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-warning</property>
<property name="icon-size">5</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">7</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="titleLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">An unknown error has occurred</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="1.1000000000000001"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="explainLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="margin_left">40</property>
<property name="margin_right">40</property>
<property name="label" translatable="yes">This program has encountered an unknown error. You may
report the bug below or quit the program.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">21</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="margin_right">20</property>
<property name="spacing">10</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="reportButton">
<property name="label" translatable="yes">_Report Bug</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_report_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="quitButton">
<property name="label" translatable="yes">_Quit</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_quit_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_bottom">10</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_expander_activated" swapped="no"/>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="commentLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">2</property>
<property name="xalign">0.10000000149011612</property>
<property name="label" translatable="yes">The output below may help determine the cause of the error:</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="tracebackScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="tracebackView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vscroll_policy">natural</property>
<property name="editable">False</property>
<property name="cursor_visible">False</property>
<property name="buffer">tracebackBuffer</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bottomBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="warningLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">'Debug' may take you to tty1.</property>
<attributes>
<attribute name="style" value="italic"/>
<attribute name="scale" value="0.80000000000000004"/>
</attributes>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="debugButton">
<property name="label" translatable="yes">_Debug</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_debug_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expanderLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">More _info...</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkTextBuffer" id="tracebackBuffer">
<property name="text" translatable="yes">No additional info available.</property>
</object>
</interface>
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<config xmlns="http://zanata.org/namespace/config/">
<url>https://fedora.zanata.org/</url>
<project>python-meh</project>
<project-version>master</project-version>
<project-type>gettext</project-type>
<locales>
<locale>sq</locale>
<locale>ar</locale>
<locale>as</locale>
<locale>ast</locale>
<locale>bal</locale>
<locale>eu</locale>
<locale>bn</locale>
<locale>bn-IN</locale>
<locale>brx</locale>
<locale>bs</locale>
<locale>br</locale>
<locale>bg</locale>
<locale>ca</locale>
<locale>zh-CN</locale>
<locale>zh-HK</locale>
<locale>zh-TW</locale>
<locale>kw</locale>
<locale>kw-GB</locale>
<locale>cs</locale>
<locale>da</locale>
<locale>nl</locale>
<locale>en-GB</locale>
<locale>eo</locale>
<locale>et</locale>
<locale>fi</locale>
<locale>fr</locale>
<locale>gl</locale>
<locale>ka</locale>
<locale>de</locale>
<locale>el</locale>
<locale>gu</locale>
<locale>he</locale>
<locale>hi</locale>
<locale>hu</locale>
<locale>is</locale>
<locale>id</locale>
<locale>ia</locale>
<locale>it</locale>
<locale>ja</locale>
<locale>kn</locale>
<locale>kk</locale>
<locale>km</locale>
<locale>ky</locale>
<locale>ko</locale>
<locale>lt</locale>
<locale>nds</locale>
<locale>mk</locale>
<locale>mai</locale>
<locale>ms</locale>
<locale>ml</locale>
<locale>mr</locale>
<locale>mn</locale>
<locale>ne</locale>
<locale>nb</locale>
<locale>nn</locale>
<locale>or</locale>
<locale>pa</locale>
<locale>fa</locale>
<locale>pl</locale>
<locale>pt</locale>
<locale>pt-BR</locale>
<locale>ro</locale>
<locale>ru</locale>
<locale>sr</locale>
<locale>sr@latin</locale>
<locale>si</locale>
<locale>sk</locale>
<locale>sl</locale>
<locale>es</locale>
<locale>sv</locale>
<locale>tg</locale>
<locale>ta</locale>
<locale>te</locale>
<locale>bo</locale>
<locale>tr</locale>
<locale>uk</locale>
<locale>ur</locale>
<locale>wba</locale>
<locale>cy</locale>
<locale>lv</locale>
<locale>kw@uccor</locale>
<locale>kw@kkcor</locale>
<locale>af</locale>
<locale>am</locale>
<locale>be</locale>
<locale>hr</locale>
<locale>de-CH</locale>
<locale>th</locale>
<locale>vi</locale>
<locale>zu</locale>
<locale>ilo</locale>
<locale>nso</locale>
<locale>tw</locale>
<locale>yo</locale>
<locale>anp</locale>
</locales>
</config>