feat(Anaconda): Local Repo
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/home/nocode/Files/FWS-ISO/pkgbuilds/python-meh/python-meh-0.43.tar.gz
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user