feat(Anaconda): Local Repo

This commit is contained in:
2026-06-13 02:06:34 +02:00
parent 05ae9c36c3
commit c412cd5d33
203 changed files with 23997 additions and 7 deletions
+29
View File
@@ -0,0 +1,29 @@
# Maintainer: FWS
# Dépendance de l'installateur Anaconda (rhinstaller) — gestion d'exceptions.
# setup.py amont = distutils + `setup.py install` : tous deux supprimés
# (Python 3.12+, setuptools 80+). Le paquet étant du pur Python, on installe
# l'arborescence à la main plutôt que de patcher la chaîne distutils.
pkgname=python-meh
pkgver=0.43
pkgrel=1
pkgdesc="Python exception handling library (dépendance Anaconda)"
arch=('any')
url="https://github.com/rhinstaller/python-meh"
license=('GPL-2.0-or-later')
depends=('python' 'python-gobject' 'python-dbus' 'libreport')
_tag="r$pkgver-1"
source=("$pkgname-$pkgver.tar.gz::https://github.com/rhinstaller/python-meh/archive/refs/tags/$_tag.tar.gz")
sha256sums=('cd50301794ab68a064715f1ace960f6de07aea809aa85f73087d44573c98be52')
_srcdir="python-meh-$_tag"
package() {
cd "$srcdir/$_srcdir"
local sitedir
sitedir=$(python -c 'import site; print(site.getsitepackages()[0])')
install -d "$pkgdir$sitedir"
cp -a meh "$pkgdir$sitedir/meh"
# -d : grave le chemin d'exécution réel dans les .pyc (pas le $pkgdir de build).
python -m compileall -q -d "$sitedir/meh" "$pkgdir$sitedir/meh" || true
install -Dm644 ui/exception-dialog.glade -t "$pkgdir/usr/share/python-meh/"
install -Dm644 COPYING "$pkgdir/usr/share/licenses/$pkgname/COPYING"
}
@@ -0,0 +1,898 @@
format = 2
pkgname = python-meh
pkgbase = python-meh
pkgver = 0.43-1
pkgarch = any
pkgbuild_sha256sum = 207bd70c0f151083b8194b40534aeed665c9af48e588230ad370757eb4368229
packager = Unknown Packager
builddate = 1781307281
builddir = /home/nocode/Files/FWS-ISO/pkgbuilds/python-meh
startdir = /home/nocode/Files/FWS-ISO/pkgbuilds/python-meh
buildtool = makepkg
buildtoolver = 7.1.0
buildenv = !distcc
buildenv = color
buildenv = !ccache
buildenv = check
buildenv = !sign
options = strip
options = docs
options = !libtool
options = !staticlibs
options = emptydirs
options = zipman
options = purge
options = debug
options = lto
installed = aardvark-dns-1.17.1-1-x86_64
installed = abseil-cpp-20260107.1-1-x86_64
installed = accountsservice-26.13.3-1-x86_64
installed = acl-2.3.2-2-x86_64
installed = adwaita-cursors-50.0-1-any
installed = adwaita-fonts-50.0-1-any
installed = adwaita-icon-theme-50.0-1-any
installed = adwaita-icon-theme-legacy-46.2-3-any
installed = alsa-card-profiles-1:1.6.6-1-x86_64
installed = alsa-lib-1.2.16-1-x86_64
installed = alsa-plugins-1:1.2.12-5-x86_64
installed = alsa-topology-conf-1.2.5.1-4-any
installed = alsa-ucm-conf-1.2.16-2-any
installed = aom-3.14.1-1-x86_64
installed = appstream-1.1.2-1-x86_64
installed = aquamarine-0.12.1-1-x86_64
installed = arch-install-scripts-31-1-any
installed = archiso-88-1-any
installed = archlinux-keyring-20260420-1-any
installed = argon2-20190702-6-x86_64
installed = at-spi2-core-2.60.4-1-x86_64
installed = atkmm-2.28.5-2-x86_64
installed = attr-2.5.2-2-x86_64
installed = audit-4.1.4-2-x86_64
installed = autoconf-2.73-1-any
installed = automake-1.18.1-1-any
installed = avahi-1:0.9rc4-1-x86_64
installed = base-3-3-any
installed = base-devel-1-2-any
installed = bash-5.3.15-1-x86_64
installed = bemoji-0.4.0-1-any
installed = binutils-2.46+r70+g155188ea10a7-1-x86_64
installed = bison-3.8.2-8-x86_64
installed = blas-3.12.1-2-x86_64
installed = bluez-libs-5.86-6-x86_64
installed = boost-libs-1.91.0-1-x86_64
installed = brightnessctl-0.5.1-3-x86_64
installed = brotli-1.2.0-1-x86_64
installed = bubblewrap-0.11.2-1-x86_64
installed = bzip2-1.0.8-6-x86_64
installed = c-ares-1.34.6-1-x86_64
installed = ca-certificates-20240618-1-any
installed = ca-certificates-mozilla-3.124-1-x86_64
installed = ca-certificates-utils-20240618-1-any
installed = cabextract-1.11-2-x86_64
installed = cairo-1.18.4-1-x86_64
installed = cairomm-1.14.6-1-x86_64
installed = cairomm-1.16-1.18.1-1-x86_64
installed = catatonit-0.2.1-2-x86_64
installed = cblas-3.12.1-2-x86_64
installed = cliphist-1:0.7.0-2-x86_64
installed = clucene-2.3.3.4-17-x86_64
installed = cmake-4.3.3-1-x86_64
installed = compiler-rt-22.1.6-1-x86_64
installed = composefs-1.0.8-1-x86_64
installed = conmon-1:2.2.1-1-x86_64
installed = containers-common-1:0.67.1-3-any
installed = coreutils-9.11-1-x86_64
installed = cpio-2.15-3-x86_64
installed = cppdap-1.58.0-3-x86_64
installed = cracklib-2.10.3-1-x86_64
installed = criu-4.2-3-x86_64
installed = crun-1.28-1-x86_64
installed = cryptsetup-2.8.6-1-x86_64
installed = curl-8.20.0-7-x86_64
installed = dav1d-1.5.3-1-x86_64
installed = db5.3-5.3.28-7-x86_64
installed = dbus-1.16.2-1-x86_64
installed = dbus-broker-37-3-x86_64
installed = dbus-broker-units-37-3-x86_64
installed = dbus-units-37-3-x86_64
installed = dconf-0.49.0-1-x86_64
installed = debugedit-5.3-1-x86_64
installed = default-cursors-3-1-any
installed = desktop-file-utils-0.28-1-x86_64
installed = device-mapper-2.03.41-1-x86_64
installed = diffutils-3.12-2-x86_64
installed = discord-1:1.0.142-1-x86_64
installed = dkms-3.4.1-1-any
installed = dos2unix-7.5.6-1-x86_64
installed = dosfstools-4.2-5-x86_64
installed = double-conversion-3.4.0-1-x86_64
installed = duktape-2.7.0-7-x86_64
installed = e2fsprogs-1.47.4-1-x86_64
installed = efibootmgr-18-4-x86_64
installed = efivar-39-2-x86_64
installed = egl-gbm-1.1.3-1-x86_64
installed = egl-wayland-4:1.1.21-1-x86_64
installed = egl-wayland2-1.0.1-1-x86_64
installed = egl-x11-1.0.5-1-x86_64
installed = eglexternalplatform-1.2.1-1-any
installed = electron41-41.7.2-1-x86_64
installed = elfutils-0.195-1-x86_64
installed = erofs-utils-1.9.1-1-x86_64
installed = exo-4.20.0-2-x86_64
installed = expat-2.8.1-1-x86_64
installed = extra-cmake-modules-6.26.0-1-any
installed = fakeroot-1:1.37.2-1-x86_64
installed = fcft-3.3.3-1-x86_64
installed = ffmpeg-2:8.1.1-2-x86_64
installed = fftw-3.3.11-1-x86_64
installed = file-5.48-1-x86_64
installed = filesystem-2025.10.12-1-any
installed = findutils-4.10.0-3-x86_64
installed = firefox-151.0.4-1-x86_64
installed = flac-1.5.0-1-x86_64
installed = flatpak-1:1.18.0-1-x86_64
installed = flex-2.6.4-6-x86_64
installed = fmt-12.1.0-2-x86_64
installed = fontconfig-2:2.18.1-1-x86_64
installed = freerdp-2:3.26.0-1-x86_64
installed = freetype2-2.14.3-1-x86_64
installed = fribidi-1.0.16-2-x86_64
installed = fuse-common-3.18.2-1-x86_64
installed = fuse2-2.9.9-5-x86_64
installed = fuse3-3.18.2-1-x86_64
installed = fuzzel-1.14.1-1-x86_64
installed = gawk-5.4.0-1-x86_64
installed = gc-8.2.12-1-x86_64
installed = gcc-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = gcr-3.41.2-2-x86_64
installed = gcr-4-4.4.0.1-1-x86_64
installed = gdb-17.2-1-x86_64
installed = gdb-common-17.2-1-x86_64
installed = gdbm-1.26-2-x86_64
installed = gdk-pixbuf2-2.44.6-2-x86_64
installed = gettext-1.0-2-x86_64
installed = giflib-6.1.3-1-x86_64
installed = git-2.54.0-1-x86_64
installed = glib-networking-1:2.80.1-1-x86_64
installed = glib2-2.88.1-1-x86_64
installed = glibc-2.43+r22+g8362e8ce10b2-2-x86_64
installed = glibmm-2.66.8-2-x86_64
installed = glibmm-2.68-2.88.0-2-x86_64
installed = glslang-1:1.4.350.0-1-x86_64
installed = glycin-2.1.1-1-x86_64
installed = gmp-6.3.0-3-x86_64
installed = gnome-themes-extra-1:3.28-1-any
installed = gnulib-l10n-20241231-1-any
installed = gnupg-2.4.9-1-x86_64
installed = gnutls-3.8.13-2-x86_64
installed = go-2:1.26.4-1-x86_64
installed = gobject-introspection-1.86.0-2-x86_64
installed = gobject-introspection-runtime-1.86.0-2-x86_64
installed = gparted-1.8.1-2-x86_64
installed = gperftools-2.18.1-1-x86_64
installed = gpgme-2.1.0-1-x86_64
installed = gpgmepp-2.1.0-1-x86_64
installed = gpm-1.20.7.r38.ge82d1a6-6-x86_64
installed = gpsd-3.27.5-1-x86_64
installed = gptfdisk-1.0.10-2-x86_64
installed = graphene-1.10.8-2-x86_64
installed = graphite-1:1.3.15-1-x86_64
installed = grep-3.12-2-x86_64
installed = grim-1.5.0-2-x86_64
installed = groff-1.24.1-1-x86_64
installed = grub-2:2.14-1-x86_64
installed = gsettings-desktop-schemas-50.1-1-any
installed = gsettings-system-schemas-50.1-1-any
installed = gsm-1.0.24-1-x86_64
installed = gssdp-1.6.5-1-x86_64
installed = gst-plugins-bad-libs-1.28.3-1-x86_64
installed = gst-plugins-base-libs-1.28.3-1-x86_64
installed = gstreamer-1.28.3-1-x86_64
installed = gtest-1.17.0-2-x86_64
installed = gtk-layer-shell-0.10.1-1-x86_64
installed = gtk-update-icon-cache-1:4.22.4-1-x86_64
installed = gtk3-1:3.24.52-1-x86_64
installed = gtk4-1:4.22.4-1-x86_64
installed = gtkmm-4.0-4.22.0-2-x86_64
installed = gtkmm3-3.24.11-1-x86_64
installed = guile-3.0.11-1-x86_64
installed = gupnp-1:1.6.10-1-x86_64
installed = gupnp-igd-1.6.0-2-x86_64
installed = gzip-1.14-2-x86_64
installed = harfbuzz-14.2.1-1-x86_64
installed = harfbuzz-icu-14.2.1-1-x86_64
installed = hicolor-icon-theme-0.18-1-any
installed = hidapi-0.15.0-1-x86_64
installed = highway-1.4.0-1-x86_64
installed = hunspell-1.7.3-1-x86_64
installed = hwdata-0.408-1-any
installed = hwloc-2.13.0-1-x86_64
installed = hyphen-2.8.9-1-x86_64
installed = hyprcursor-0.1.13-6-x86_64
installed = hyprgraphics-0.5.1-2-x86_64
installed = hypridle-0.1.7-9-x86_64
installed = hyprland-0.55.4-1-x86_64
installed = hyprland-guiutils-0.2.1-6-x86_64
installed = hyprland-qt-support-0.1.0-12-x86_64
installed = hyprlang-0.6.8-4-x86_64
installed = hyprlock-0.9.5-4-x86_64
installed = hyprpaper-0.8.4-2-x86_64
installed = hyprpicker-0.4.7-1-x86_64
installed = hyprpolkitagent-0.1.3-7-x86_64
installed = hyprshot-1.3.0-4-any
installed = hyprtoolkit-0.5.4-2-x86_64
installed = hyprutils-0.13.1-1-x86_64
installed = hyprwayland-scanner-0.4.6-1-x86_64
installed = hyprwire-0.3.1-2-x86_64
installed = iana-etc-20260530-1-any
installed = icu-78.3-1-x86_64
installed = imagemagick-7.1.2.25-1-x86_64
installed = iniparser-4.2.6-2-x86_64
installed = iproute2-7.0.0-1-x86_64
installed = iptables-1:1.8.13-1-x86_64
installed = iputils-20250605-1-x86_64
installed = iso-codes-4.20.1-1-any
installed = jack2-1.9.22-2-x86_64
installed = jansson-2.15.0-1-x86_64
installed = jbigkit-2.1-8-x86_64
installed = jq-1.8.1-3-x86_64
installed = json-c-0.18-2-x86_64
installed = json-glib-1.10.8-1-x86_64
installed = jsoncpp-1.9.6-3-x86_64
installed = kbd-2.10.0-1-x86_64
installed = kcoreaddons-6.26.0-1-x86_64
installed = keyutils-1.6.3-4-x86_64
installed = ki18n-6.26.0-1-x86_64
installed = kitty-0.47.1-1-x86_64
installed = kitty-shell-integration-0.47.1-1-x86_64
installed = kitty-terminfo-0.47.1-1-x86_64
installed = kmod-34.2-1-x86_64
installed = kpmcore-26.04.2-1-x86_64
installed = krb5-1.22.2-1-x86_64
installed = kwidgetsaddons-6.26.0-1-x86_64
installed = l-smash-2.14.5-4-x86_64
installed = lame-3.101.r6531-1-x86_64
installed = lapack-3.12.1-2-x86_64
installed = lcms2-2.19.1-1-x86_64
installed = leancrypto-1.7.2-1-x86_64
installed = lib32-alsa-lib-1.2.16-1-x86_64
installed = lib32-alsa-plugins-1.2.12-1-x86_64
installed = lib32-brotli-1.1.0-1-x86_64
installed = lib32-bzip2-1.0.8-4-x86_64
installed = lib32-curl-8.20.0-7-x86_64
installed = lib32-dbus-1.16.2-1-x86_64
installed = lib32-e2fsprogs-1.47.4-1-x86_64
installed = lib32-expat-2.8.1-1-x86_64
installed = lib32-fontconfig-2:2.18.1-1-x86_64
installed = lib32-freetype2-2.14.3-1-x86_64
installed = lib32-gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = lib32-glib2-2.88.1-1-x86_64
installed = lib32-glibc-2.43+r22+g8362e8ce10b2-2-x86_64
installed = lib32-gmp-6.3.0-2-x86_64
installed = lib32-gnutls-3.8.13-3-x86_64
installed = lib32-icu-78.3-1-x86_64
installed = lib32-json-c-0.18-2-x86_64
installed = lib32-keyutils-1.6.3-4-x86_64
installed = lib32-krb5-1.22.2-1-x86_64
installed = lib32-libdrm-2.4.134-1-x86_64
installed = lib32-libelf-0.195-1-x86_64
installed = lib32-libffi-3.5.2-1-x86_64
installed = lib32-libgcrypt-1.12.2-1-x86_64
installed = lib32-libglvnd-1.7.0-1-x86_64
installed = lib32-libgpg-error-1.61-1-x86_64
installed = lib32-libidn2-2.3.8-1-x86_64
installed = lib32-libldap-2.6.13-1-x86_64
installed = lib32-libnghttp2-1.69.0-1-x86_64
installed = lib32-libnghttp3-1.16.0-1-x86_64
installed = lib32-libngtcp2-1.23.0-1-x86_64
installed = lib32-libnm-1.56.1-1-x86_64
installed = lib32-libpciaccess-0.19-1-x86_64
installed = lib32-libpipewire-1:1.6.6-1-x86_64
installed = lib32-libpng-1.6.58-1-x86_64
installed = lib32-libpsl-0.21.5-1-x86_64
installed = lib32-libssh2-1.11.1-1-x86_64
installed = lib32-libtasn1-4.21.0-1-x86_64
installed = lib32-libunistring-1.4.2-1-x86_64
installed = lib32-libva-2.22.0-1-x86_64
installed = lib32-libx11-1.8.13-1-x86_64
installed = lib32-libxau-1.0.12-1-x86_64
installed = lib32-libxcb-1.17.0-1-x86_64
installed = lib32-libxcrypt-4.5.2-1-x86_64
installed = lib32-libxcrypt-compat-4.5.2-1-x86_64
installed = lib32-libxdmcp-1.1.5-1-x86_64
installed = lib32-libxext-1.3.7-1-x86_64
installed = lib32-libxfixes-6.0.1-2-x86_64
installed = lib32-libxinerama-1.1.6-1-x86_64
installed = lib32-libxml2-2.15.3-1-x86_64
installed = lib32-libxshmfence-1.3.3-1-x86_64
installed = lib32-libxss-1.2.5-1-x86_64
installed = lib32-libxxf86vm-1.1.5-2-x86_64
installed = lib32-llvm-libs-1:22.1.6-1-x86_64
installed = lib32-lm_sensors-1:3.6.2-2-x86_64
installed = lib32-mesa-1:26.1.2-1-x86_64
installed = lib32-ncurses-6.6-2-x86_64
installed = lib32-nettle-4.0-2-x86_64
installed = lib32-nspr-4.39-1-x86_64
installed = lib32-nss-3.124-1-x86_64
installed = lib32-nvidia-utils-610.43.02-1-x86_64
installed = lib32-openssl-1:3.6.3-1-x86_64
installed = lib32-p11-kit-0.26.2-1-x86_64
installed = lib32-pcre2-10.47-1-x86_64
installed = lib32-pipewire-1:1.6.6-1-x86_64
installed = lib32-spirv-tools-1:1.4.350.0-1-x86_64
installed = lib32-sqlite-3.53.2-1-x86_64
installed = lib32-systemd-260.2-1-x86_64
installed = lib32-util-linux-2.42.1-1-x86_64
installed = lib32-vulkan-icd-loader-1.4.350.0-1-x86_64
installed = lib32-wayland-1.25.0-1-x86_64
installed = lib32-xz-5.8.3-1-x86_64
installed = lib32-zlib-1.3.2-1-x86_64
installed = lib32-zstd-1.5.7-2-x86_64
installed = libabw-0.1.4-1-x86_64
installed = libadwaita-1:1.9.1-1-x86_64
installed = libaio-0.3.113-4-x86_64
installed = libappindicator-12.10.1-1-x86_64
installed = libarchive-3.8.7-1-x86_64
installed = libasan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libass-0.17.4-2-x86_64
installed = libassuan-3.0.0-1-x86_64
installed = libasyncns-1:0.8+r3+g68cd5af-3-x86_64
installed = libatomic-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libatomic_ops-7.8.2-2-x86_64
installed = libavc1394-0.5.4-7-x86_64
installed = libavif-1.4.2-1-x86_64
installed = libb2-0.98.1-3-x86_64
installed = libblake3-1.8.4-1-x86_64
installed = libbluray-1.4.1-1-x86_64
installed = libbpf-1.7.0-1-x86_64
installed = libbs2b-3.1.0-10-x86_64
installed = libbsd-0.12.2-2-x86_64
installed = libburn-1.5.8-1-x86_64
installed = libcanberra-1:0.30+r2+gc0620e4-6-x86_64
installed = libcap-2.78-1-x86_64
installed = libcap-ng-0.9.3-1-x86_64
installed = libcbor-0.14.0-1-x86_64
installed = libcdio-2.3.0-1-x86_64
installed = libcdio-paranoia-10.2+2.0.2-2-x86_64
installed = libcdr-0.1.9-1-x86_64
installed = libcloudproviders-0.4.0-1-x86_64
installed = libcmis-0.6.3-1-x86_64
installed = libcolord-1.4.8-1-x86_64
installed = libcups-2:2.4.19-1-x86_64
installed = libdaemon-0.14-6-x86_64
installed = libdatrie-0.2.14-1-x86_64
installed = libdbusmenu-glib-18.10.20180917-1-x86_64
installed = libdbusmenu-gtk3-18.10.20180917-1-x86_64
installed = libdecor-0.2.5-1-x86_64
installed = libdeflate-1.25-1-x86_64
installed = libdisplay-info-0.3.0-1-x86_64
installed = libdovi-3.3.2-1-x86_64
installed = libdrm-2.4.134-1-x86_64
installed = libdvdnav-7.0.0-1-x86_64
installed = libdvdread-7.0.1-1-x86_64
installed = libe-book-0.1.3-20-x86_64
installed = libebur128-1.2.6-2-x86_64
installed = libedit-20260512_3.1-1-x86_64
installed = libei-1.6.0-1-x86_64
installed = libelf-0.195-1-x86_64
installed = libepoxy-1.5.10-3-x86_64
installed = libepubgen-0.1.1-6-x86_64
installed = libetonyek-0.1.13-2-x86_64
installed = libevdev-1.13.6-1-x86_64
installed = libevent-2.1.12-5-x86_64
installed = libexif-0.6.26-1-x86_64
installed = libexttextcat-3.4.8-1-x86_64
installed = libfdk-aac-2.0.3-2-x86_64
installed = libffi-3.5.2-1-x86_64
installed = libfido2-1.17.0-1-x86_64
installed = libfontenc-1.1.9-1-x86_64
installed = libfreeaptx-0.2.2-1-x86_64
installed = libfreehand-0.1.3-1-x86_64
installed = libfyaml-0.9.6-2-x86_64
installed = libgcc-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libgcrypt-1.12.2-1-x86_64
installed = libgfortran-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libgirepository-1.86.0-2-x86_64
installed = libgit2-1:1.9.4-1-x86_64
installed = libglvnd-1.7.0-3-x86_64
installed = libgomp-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libgpg-error-1.61-1-x86_64
installed = libgtop-2.41.3-2-x86_64
installed = libgudev-238-3-x86_64
installed = libice-1.1.2-1-x86_64
installed = libidn2-2.3.8-1-x86_64
installed = libiec61883-1.2.0-9-x86_64
installed = libimobiledevice-1.4.0-2-x86_64
installed = libimobiledevice-glue-1.3.2-1-x86_64
installed = libinih-62-2-x86_64
installed = libinput-1.31.3-1-x86_64
installed = libisl-0.27-1-x86_64
installed = libisoburn-1.5.8.2-1-x86_64
installed = libisofs-1.5.8.2-1-x86_64
installed = libixion-0.20.0-7-x86_64
installed = libjpeg-turbo-3.1.4.1-1-x86_64
installed = libjxl-0.11.2-2-x86_64
installed = libksba-1.8.0-1-x86_64
installed = liblangtag-0.6.8-1-x86_64
installed = liblc3-1.1.3-2-x86_64
installed = libldac-2.0.2.3-3-x86_64
installed = libldap-2.6.13-1-x86_64
installed = liblqr-0.4.3-1-x86_64
installed = liblsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libmakepkg-dropins-20-1-any
installed = libmalcontent-0.14.0-4-x86_64
installed = libmd-1.2.0-1-x86_64
installed = libmm-glib-1.24.2-1-x86_64
installed = libmnl-1.0.5-2-x86_64
installed = libmodplug-0.8.9.0-7-x86_64
installed = libmpc-1.4.1-1-x86_64
installed = libmpdclient-2.23-1-x86_64
installed = libmspub-0.1.5-1-x86_64
installed = libmwaw-0.3.22-4-x86_64
installed = libmysofa-1.3.4-1-x86_64
installed = libndp-1.9-1-x86_64
installed = libnet-2:1.3-2-x86_64
installed = libnetfilter_conntrack-1.1.1-1-x86_64
installed = libnewt-0.52.25-2-x86_64
installed = libnfnetlink-1.0.2-2-x86_64
installed = libnftnl-1.3.1-1-x86_64
installed = libnghttp2-1.69.0-1-x86_64
installed = libnghttp3-1.16.0-1-x86_64
installed = libngtcp2-1.23.0-1-x86_64
installed = libnice-0.1.23-1-x86_64
installed = libnl-3.12.0-1-x86_64
installed = libnm-1.56.1-1-x86_64
installed = libnma-1.10.6-3-x86_64
installed = libnma-common-1.10.6-3-x86_64
installed = libnotify-0.8.8-1-x86_64
installed = libnsl-2.0.1-2-x86_64
installed = libnumbertext-1.0.11-3-x86_64
installed = libobjc-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libodfgen-0.1.8-5-x86_64
installed = libogg-1.3.6-1-x86_64
installed = libopenmpt-0.8.7-1-x86_64
installed = libopenrazer-0.4.0-1-x86_64
installed = liborcus-0.21.0-6-x86_64
installed = libp11-kit-0.26.2-1-x86_64
installed = libpagemaker-0.0.4-5-x86_64
installed = libpcap-1.10.6-1-x86_64
installed = libpciaccess-0.19-1-x86_64
installed = libpgm-5.3.128-4-x86_64
installed = libpipewire-1:1.6.6-1-x86_64
installed = libplacebo-7.360.1-2-x86_64
installed = libplist-2.7.0-3-x86_64
installed = libpng-1.6.58-1-x86_64
installed = libproxy-0.5.12-1-x86_64
installed = libpsl-0.21.5-2-x86_64
installed = libpulse-17.0+r98+gb096704c0-1-x86_64
installed = libpwquality-1.4.5-7-x86_64
installed = libquadmath-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libqxp-0.0.3-1-x86_64
installed = libraqm-0.10.5-1-x86_64
installed = libraw1394-2.1.2-4-x86_64
installed = libreoffice-fresh-26.2.4-1-x86_64
installed = librevenge-0.0.5-4-x86_64
installed = librsvg-2:2.62.3-1-x86_64
installed = librsync-1:2.3.4-2-x86_64
installed = libsamplerate-0.2.2-3-x86_64
installed = libsasl-2.1.28-5-x86_64
installed = libseccomp-2.6.0-1-x86_64
installed = libsecret-0.21.7-1-x86_64
installed = libsigc++-2.12.2-1-x86_64
installed = libsigc++-3.0-3.8.1-1-x86_64
installed = libsixel-1.10.5-1-x86_64
installed = libsm-1.2.6-1-x86_64
installed = libsndfile-1.2.2-4-x86_64
installed = libsodium-1.0.22-1-x86_64
installed = libsoup3-3.6.6-2-x86_64
installed = libsoxr-0.1.3-4-x86_64
installed = libssh-0.12.0-1-x86_64
installed = libssh2-1.11.1-1-x86_64
installed = libstaroffice-0.0.7-5-x86_64
installed = libstdc++-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libstemmer-3.1.1-1-x86_64
installed = libsysprof-capture-50.0-2-x86_64
installed = libtasn1-4.21.0-1-x86_64
installed = libtatsu-1.0.5-1-x86_64
installed = libteam-1.32-3-x86_64
installed = libthai-0.1.30-1-x86_64
installed = libtheora-1.2.0-1-x86_64
installed = libtiff-4.7.1-2-x86_64
installed = libtirpc-1.3.7-1-x86_64
installed = libtommath-1.3.0-2-x86_64
installed = libtool-2.6.1-1-x86_64
installed = libtsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libubsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libunibreak-7.0-1-x86_64
installed = libunistring-1.4.2-1-x86_64
installed = libunwind-1.8.2-1-x86_64
installed = liburing-2.14-1-x86_64
installed = libusb-1.0.30-1-x86_64
installed = libusbmuxd-2.1.1-2-x86_64
installed = libutf8proc-2.11.3-1-x86_64
installed = libuv-1.52.1-1-x86_64
installed = libva-2.23.0-1-x86_64
installed = libva-nvidia-driver-0.0.17-1-x86_64
installed = libvdpau-1.5-4-x86_64
installed = libverto-0.3.2-6-x86_64
installed = libvisio-0.1.11-1-x86_64
installed = libvorbis-1.3.7-4-x86_64
installed = libvpl-2.16.0-2-x86_64
installed = libvpx-1.16.0-3-x86_64
installed = libwacom-2.19.0-1-x86_64
installed = libwebp-1.6.0-2-x86_64
installed = libwireplumber-0.5.14-1-x86_64
installed = libwpd-0.10.3-6-x86_64
installed = libwps-0.4.14-4-x86_64
installed = libx11-1.8.13-1-x86_64
installed = libxau-1.0.12-1-x86_64
installed = libxcb-1.17.0-1-x86_64
installed = libxcomposite-0.4.7-1-x86_64
installed = libxcrypt-4.5.2-1-x86_64
installed = libxcrypt-compat-4.5.2-1-x86_64
installed = libxcursor-1.2.3-1-x86_64
installed = libxcvt-0.1.3-1-x86_64
installed = libxdamage-1.1.7-1-x86_64
installed = libxdmcp-1.1.5-2-x86_64
installed = libxext-1.3.7-1-x86_64
installed = libxfce4ui-4.20.2-1-x86_64
installed = libxfce4util-4.20.1-1-x86_64
installed = libxfixes-6.0.2-1-x86_64
installed = libxfont2-2.0.7-1-x86_64
installed = libxft-2.3.9-1-x86_64
installed = libxi-1.8.3-1-x86_64
installed = libxinerama-1.1.6-1-x86_64
installed = libxkbcommon-1.13.2-1-x86_64
installed = libxkbcommon-x11-1.13.2-1-x86_64
installed = libxkbfile-1.2.0-1-x86_64
installed = libxml2-2.15.3-1-x86_64
installed = libxml2-legacy-2.13.9-2-x86_64
installed = libxmlb-0.3.27-1-x86_64
installed = libxmp-4.7.0-1-x86_64
installed = libxmu-1.3.1-1-x86_64
installed = libxpresent-1.0.2-1-x86_64
installed = libxrandr-1.5.5-1-x86_64
installed = libxrender-0.9.12-1-x86_64
installed = libxshmfence-1.3.3-1-x86_64
installed = libxslt-1.1.45-2-x86_64
installed = libxss-1.2.5-1-x86_64
installed = libxt-1.3.1-1-x86_64
installed = libxtst-1.2.5-1-x86_64
installed = libxv-1.0.13-1-x86_64
installed = libxxf86vm-1.1.7-1-x86_64
installed = libyaml-0.2.5-3-x86_64
installed = libyuv-r2426+464c51a03-1-x86_64
installed = libzip-1.11.4-1-x86_64
installed = libzmf-0.0.2-20-x86_64
installed = licenses-20240728-1-any
installed = lilv-0.26.4-1-x86_64
installed = linux-7.0.11.arch1-1-x86_64
installed = linux-api-headers-7.0-1-x86_64
installed = linux-firmware-20260519-1-any
installed = linux-firmware-amdgpu-20260519-1-any
installed = linux-firmware-atheros-20260519-1-any
installed = linux-firmware-broadcom-20260519-1-any
installed = linux-firmware-cirrus-20260519-1-any
installed = linux-firmware-intel-20260519-1-any
installed = linux-firmware-mediatek-20260519-1-any
installed = linux-firmware-nvidia-20260519-1-any
installed = linux-firmware-other-20260519-1-any
installed = linux-firmware-radeon-20260519-1-any
installed = linux-firmware-realtek-20260519-1-any
installed = linux-firmware-whence-20260519-1-any
installed = linux-headers-7.0.11.arch1-1-x86_64
installed = lld-22.1.6-1-x86_64
installed = llhttp-9.3.1-1-x86_64
installed = llvm-libs-22.1.6-1-x86_64
installed = lm_sensors-1:3.6.2-1-x86_64
installed = lmdb-0.9.35-1-x86_64
installed = lpsolve-5.5.2.14-1-x86_64
installed = lsb-release-2.0.r55.a25a4fc-1-any
installed = lsof-4.99.6-1-x86_64
installed = lua-5.5.0-2-x86_64
installed = lua54-5.4.8-6-x86_64
installed = luajit-2.1.1780076327+b925b3e-1-x86_64
installed = lug-helper-1:4.13-1-any
installed = lv2-1.18.10-2-x86_64
installed = lz4-1:1.10.0-2-x86_64
installed = lzo-2.10-5-x86_64
installed = m4-1.4.21-2-x86_64
installed = mailcap-2.1.54-2-any
installed = make-4.4.1-3-x86_64
installed = mako-1.11.0-1-x86_64
installed = md4c-0.5.3-1-x86_64
installed = mesa-1:26.1.2-1-x86_64
installed = meson-1.11.1-3-any
installed = mingw-w64-binutils-2.46.0-1-x86_64
installed = mingw-w64-crt-14.0.0-1-any
installed = mingw-w64-gcc-16.1.0-1-x86_64
installed = mingw-w64-headers-14.0.0-1-any
installed = mingw-w64-winpthreads-14.0.0-1-any
installed = minizip-1:1.3.2-3-x86_64
installed = mkinitcpio-41-4-any
installed = mkinitcpio-busybox-1.36.1-1-x86_64
installed = mobile-broadband-provider-info-20251101-1-any
installed = mpdecimal-4.0.1-3-x86_64
installed = mpfr-4.2.2-1-x86_64
installed = mpg123-1.33.5-1-x86_64
installed = mpv-1:0.41.0-3-x86_64
installed = mpvpaper-1.8-1-x86_64
installed = mtdev-1.1.7-1-x86_64
installed = mtools-1:4.0.49-1-x86_64
installed = mujs-1.3.9-1-x86_64
installed = muparser-2.3.5-2-x86_64
installed = nano-9.0-1-x86_64
installed = ncurses-6.6-2-x86_64
installed = neon-0.37.1-1-x86_64
installed = netavark-1.17.2-2-x86_64
installed = nettle-4.0-1-x86_64
installed = network-manager-applet-1.36.0-2-x86_64
installed = networkmanager-1.56.1-1-x86_64
installed = nftables-1:1.1.6-3-x86_64
installed = ninja-1.13.2-3-x86_64
installed = nm-connection-editor-1.36.0-2-x86_64
installed = noto-fonts-1:2026.06.01-1-any
installed = noto-fonts-cjk-20240730-1-any
installed = noto-fonts-emoji-1:2.051-1-any
installed = noto-fonts-extra-1:2026.06.01-1-any
installed = npth-1.8-1-x86_64
installed = nspr-4.39-1-x86_64
installed = nss-3.124-1-x86_64
installed = nvidia-open-610.43.02-2-x86_64
installed = nvidia-utils-610.43.02-2-x86_64
installed = ocl-icd-2.3.4-1-x86_64
installed = onetbb-2023.0.0-1-x86_64
installed = oniguruma-6.9.10-1-x86_64
installed = openal-1.25.2-1-x86_64
installed = opencore-amr-0.1.6-2-x86_64
installed = openjpeg2-2.5.4-1-x86_64
installed = openrazer-daemon-3.12.2-2-any
installed = openrazer-driver-dkms-3.12.2-2-any
installed = openssh-10.3p1-1-x86_64
installed = openssl-3.6.3-1-x86_64
installed = opus-1.6.1-1-x86_64
installed = opusfile-0.12-4-x86_64
installed = orc-0.4.42-1-x86_64
installed = os-prober-1.84-1-x86_64
installed = ostree-2025.7-3-x86_64
installed = p11-kit-0.26.2-1-x86_64
installed = pacman-7.1.0.r9.g54d9411-2-x86_64
installed = pacman-mirrorlist-20260610-1-any
installed = pahole-1:1.31-2-x86_64
installed = pam-1.7.2-2-x86_64
installed = pambase-20250719-1-any
installed = pango-1:1.57.1-1-x86_64
installed = pangomm-2.46.4-3-x86_64
installed = pangomm-2.48-2.56.1-2-x86_64
installed = papirus-icon-theme-20250501-1-any
installed = parted-3.7-1-x86_64
installed = paru-2.1.0-2-x86_64
installed = paru-debug-2.1.0-2-x86_64
installed = passt-2026_05_26.038c51e-1-x86_64
installed = patch-2.8-1-x86_64
installed = pavucontrol-1:6.2-1-x86_64
installed = pciutils-3.15.0-1-x86_64
installed = pcre-8.45-4-x86_64
installed = pcre2-10.47-1-x86_64
installed = pcsclite-2.5.1-1-x86_64
installed = perl-5.42.2-1-x86_64
installed = perl-error-0.17030-3-any
installed = perl-mailtools-2.22-3-any
installed = perl-timedate-2.35-1-any
installed = pinentry-1.3.2-2-x86_64
installed = pipewire-1:1.6.6-1-x86_64
installed = pipewire-audio-1:1.6.6-1-x86_64
installed = pipewire-pulse-1:1.6.6-1-x86_64
installed = pixman-0.46.4-1-x86_64
installed = pkgconf-2.5.1-1-x86_64
installed = playerctl-2.4.1-5-x86_64
installed = podman-5.8.3-1-x86_64
installed = podman-compose-1.6.0-1-any
installed = podman-desktop-1.27.2-2-x86_64
installed = polkit-127-3-x86_64
installed = polkit-qt6-0.201.1-1-x86_64
installed = poppler-26.05.0-1-x86_64
installed = popt-1.19-2-x86_64
installed = portaudio-1:19.7.0-4-x86_64
installed = portmidi-1:2.0.8-1-x86_64
installed = pps-tools-1.0.3-2-x86_64
installed = procps-ng-4.0.6-1-x86_64
installed = protobuf-35.0-1-x86_64
installed = protobuf-c-1.5.2-10-x86_64
installed = psmisc-23.7-2-x86_64
installed = pugixml-1.15-3-x86_64
installed = python-3.14.5-1-x86_64
installed = python-attrs-26.1.0-1-any
installed = python-autocommand-2.2.2-9-any
installed = python-cffi-2.0.0-2-x86_64
installed = python-click-8.3.3-1-any
installed = python-cryptography-48.0.1-1-x86_64
installed = python-daemonize-2.5.0-10-any
installed = python-dbus-1.4.0-2-x86_64
installed = python-dotenv-1.2.2-1-any
installed = python-filelock-3.29.0-1-any
installed = python-gobject-3.56.3-1-x86_64
installed = python-jaraco.collections-5.1.0-3-any
installed = python-jaraco.context-6.1.2-1-any
installed = python-jaraco.functools-4.1.0-3-any
installed = python-jaraco.text-4.0.0-4-any
installed = python-mako-1.3.11-1-any
installed = python-markdown-3.10.2-1-any
installed = python-markupsafe-3.0.3-1-x86_64
installed = python-more-itertools-11.1.0-1-any
installed = python-numpy-2.4.6-1-x86_64
installed = python-openrazer-3.12.2-2-any
installed = python-packaging-26.2-1-any
installed = python-pip-26.1.2-1-any
installed = python-pkg_resources-81.0.0-1-any
installed = python-platformdirs-4.10.0-1-any
installed = python-protobuf-35.0-1-x86_64
installed = python-pycparser-3.00-1-any
installed = python-pygame-2.6.1-5-x86_64
installed = python-pyudev-0.24.4-1-any
installed = python-setproctitle-1.3.7-2-x86_64
installed = python-setuptools-1:82.0.1-1-any
installed = python-tqdm-4.68.1-1-any
installed = python-typing_extensions-4.15.0-3-any
installed = python-wheel-0.47.0-1-any
installed = python-yaml-6.0.3-2-x86_64
installed = qt5-base-5.15.19+kde+r96-1-x86_64
installed = qt5-declarative-5.15.19+kde+r23-1-x86_64
installed = qt5-graphicaleffects-5.15.19-1-x86_64
installed = qt5-quickcontrols2-5.15.19+kde+r5-1-x86_64
installed = qt5-svg-5.15.19+kde+r5-1-x86_64
installed = qt5-translations-5.15.19-1-any
installed = qt5-wayland-5.15.19+kde+r55-1-x86_64
installed = qt6-5compat-6.11.1-1-x86_64
installed = qt6-base-6.11.1-1-x86_64
installed = qt6-declarative-6.11.1-3-x86_64
installed = qt6-shadertools-6.11.1-1-x86_64
installed = qt6-svg-6.11.1-1-x86_64
installed = qt6-tools-6.11.1-1-x86_64
installed = qt6-translations-6.11.1-1-any
installed = qt6-wayland-6.11.1-1-x86_64
installed = raptor-2.0.16-9-x86_64
installed = rasqal-1:0.9.33-9-x86_64
installed = rav1e-0.8.1-2-x86_64
installed = razergenie-1.3.0-1-x86_64
installed = re2-2:2025.11.05-4-x86_64
installed = readline-8.3.003-1-x86_64
installed = redland-1:1.0.17-10-x86_64
installed = reflector-2023-5-any
installed = resvg-0.47.0-2-x86_64
installed = rhash-1.4.6-1-x86_64
installed = rsync-3.4.4-1-x86_64
installed = rtkit-0.14-1-x86_64
installed = rubberband-4.0.0-2-x86_64
installed = rustup-1.29.0-2-x86_64
installed = sbc-2.2-1-x86_64
installed = scdoc-1.11.4-1-x86_64
installed = sdbus-cpp-2.3.1-1-x86_64
installed = sddm-0.21.0-7-x86_64
installed = sddm-catppuccin-git-r42.10831de-1-any
installed = sdl2-compat-2.32.70-1-x86_64
installed = sdl2_image-2.8.12-1-x86_64
installed = sdl2_mixer-2.8.2-1-x86_64
installed = sdl2_ttf-2.24.0-2-x86_64
installed = sdl3-3.4.10-1-x86_64
installed = sdl3_ttf-3.2.2-3-x86_64
installed = seatd-0.9.3-1-x86_64
installed = sed-4.10-1-x86_64
installed = serd-0.32.10-1-x86_64
installed = shaderc-2026.2-1-x86_64
installed = shadow-4.18.0-1-x86_64
installed = shared-mime-info-2.4-3-x86_64
installed = slang-2.3.3-4-x86_64
installed = slurp-1.5.0-2-x86_64
installed = smartmontools-7.5-1-x86_64
installed = snappy-1.2.2-3-x86_64
installed = sndio-1.10.0-1-x86_64
installed = sord-0.16.22-1-x86_64
installed = sound-theme-freedesktop-0.8-6-any
installed = source-highlight-3.1.9-18-x86_64
installed = spandsp-0.0.6-7-x86_64
installed = spdlog-1.17.0-2-x86_64
installed = speex-1.2.1-2-x86_64
installed = speexdsp-1.2.1-2-x86_64
installed = spirv-tools-1:1.4.350.0-1-x86_64
installed = sqlite-3.53.2-1-x86_64
installed = squashfs-tools-4.7.5-1-x86_64
installed = sratom-0.6.22-1-x86_64
installed = srt-1.5.5-1-x86_64
installed = startup-notification-0.12-9-x86_64
installed = steam-1.0.0.85-7-x86_64
installed = steam-devices-1.0.0.85-7-x86_64
installed = sudo-1.9.17.p2-2-x86_64
installed = svt-av1-4.1.0-1-x86_64
installed = systemd-260.2-2-x86_64
installed = systemd-libs-260.2-2-x86_64
installed = systemd-sysvcompat-260.2-2-x86_64
installed = tar-1.35-2-x86_64
installed = tcl-8.6.16-1-x86_64
installed = tdb-1.4.15-1-x86_64
installed = texinfo-7.3-1-x86_64
installed = thunar-4.20.8-3-x86_64
installed = tinysparql-3.11.1-1-x86_64
installed = tk-8.6.16-1-x86_64
installed = tomlplusplus-3.4.0-2-x86_64
installed = tpm2-tss-4.1.3-1-x86_64
installed = tslib-1.24-1-x86_64
installed = ttf-jetbrains-mono-nerd-3.4.0-2-any
installed = tzdata-2026b-1-x86_64
installed = uchardet-0.0.8-4-x86_64
installed = uefitool-debug-1:0.28.0-3-x86_64
installed = unzip-6.0-23-x86_64
installed = upower-1.91.2-1-x86_64
installed = usbutils-019-1-x86_64
installed = util-linux-2.42.1-1-x86_64
installed = util-linux-libs-2.42.1-1-x86_64
installed = v4l-utils-1.32.0-2-x86_64
installed = vapoursynth-76-1-x86_64
installed = vid.stab-1.1.1-2-x86_64
installed = vim-9.2.0623-1-x86_64
installed = vim-runtime-9.2.0623-1-x86_64
installed = vmaf-3.1.0-1-x86_64
installed = vmware-keymaps-1.0-3-any
installed = vmware-workstation-26H1-3-x86_64
installed = vscodium-bin-1.121.03429-1-x86_64
installed = vulkan-icd-loader-1.4.350.0-1-x86_64
installed = vulkan-tools-1.4.350.0-1-x86_64
installed = waybar-0.15.0-2-x86_64
installed = wayland-1.25.0-1-x86_64
installed = wayland-protocols-1.49-1-any
installed = webrtc-audio-processing-1-1.3-5-x86_64
installed = which-2.25-1-x86_64
installed = winboat-bin-0.9.0-1-x86_64
installed = wine-staging-11.10-1-x86_64
installed = wireplumber-0.5.14-1-x86_64
installed = wl-clipboard-1:2.3.0-1-x86_64
installed = wlogout-1.2.2-0-x86_64
installed = woff2-font-awesome-7.2.0-1-any
installed = wpa_supplicant-2:2.11-5-x86_64
installed = x264-3:0.165.r3222.b35605a-2-x86_64
installed = x265-4.1-1-x86_64
installed = xautomation-1.09-6-x86_64
installed = xcb-proto-1.17.0-4-any
installed = xcb-util-0.4.1-2-x86_64
installed = xcb-util-cursor-0.1.6-1-x86_64
installed = xcb-util-errors-1.0.1-2-x86_64
installed = xcb-util-image-0.4.1-3-x86_64
installed = xcb-util-keysyms-0.4.1-5-x86_64
installed = xcb-util-renderutil-0.3.10-2-x86_64
installed = xcb-util-wm-0.4.2-2-x86_64
installed = xdg-dbus-proxy-0.1.7-1-x86_64
installed = xdg-desktop-portal-1.22.0-1-x86_64
installed = xdg-desktop-portal-gtk-1.15.3-1-x86_64
installed = xdg-desktop-portal-hyprland-1.3.12-2-x86_64
installed = xdg-user-dirs-0.20-1-x86_64
installed = xdg-utils-1.2.1-2-any
installed = xf86-input-libinput-1.5.0-1-x86_64
installed = xfconf-4.20.0-2-x86_64
installed = xkeyboard-config-2.47-1-any
installed = xmlsec-1.3.11-1-x86_64
installed = xorg-fonts-encodings-1.1.0-2-any
installed = xorg-server-21.1.23-1-x86_64
installed = xorg-server-common-21.1.23-1-x86_64
installed = xorg-setxkbmap-1.3.5-1-x86_64
installed = xorg-xauth-1.1.5-1-x86_64
installed = xorg-xkbcomp-1.5.0-1-x86_64
installed = xorg-xprop-1.2.8-1-x86_64
installed = xorg-xrandr-1.5.4-1-x86_64
installed = xorg-xwayland-24.1.12-1-x86_64
installed = xorgproto-2025.1-1-any
installed = xvidcore-1.3.7-3-x86_64
installed = xxhash-0.8.3-1-x86_64
installed = xz-5.8.3-1-x86_64
installed = yaml-cpp-0.9.0-1-x86_64
installed = yay-12.6.0-1-x86_64
installed = yay-debug-12.6.0-1-x86_64
installed = zenity-4.2.2-1-x86_64
installed = zeromq-4.3.5-3-x86_64
installed = zimg-3.0.6-1-x86_64
installed = zint-2.16.0-2-x86_64
installed = zix-0.8.0-1-x86_64
installed = zlib-1:1.3.2-3-x86_64
installed = zlib-ng-2.3.3-1-x86_64
installed = zstd-1.5.7-3-x86_64
installed = zxing-cpp-3.0.2-1-x86_64
Binary file not shown.
@@ -0,0 +1,16 @@
# Generated by makepkg 7.1.0
# using fakeroot version 1.37.2
pkgname = python-meh
pkgbase = python-meh
xdata = pkgtype=pkg
pkgver = 0.43-1
pkgdesc = Python exception handling library (dépendance Anaconda)
url = https://github.com/rhinstaller/python-meh
builddate = 1781307281
packager = Unknown Packager
size = 170289
arch = any
license = GPL-2.0-or-later
depend = python
depend = python-gobject
depend = python-dbus
@@ -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)
@@ -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,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>
Binary file not shown.
+1
View File
@@ -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>
+27
View File
@@ -0,0 +1,27 @@
# Maintainer: FWS
# Dépendance de l'installateur Anaconda (rhinstaller) — framework TUI texte.
# Pur Python (setuptools find_packages, aucune extension C) : on installe
# l'arborescence à la main pour éviter d'exiger python-build/python-installer.
pkgname=python-simpleline
pkgver=1.8
pkgrel=1
pkgdesc="Python3 text UI framework (dépendance Anaconda)"
arch=('any')
url="https://github.com/rhinstaller/python-simpleline"
license=('LGPL-3.0-or-later')
depends=('python' 'python-gobject')
_tag="simpleline-$pkgver"
source=("$pkgname-$pkgver.tar.gz::https://github.com/rhinstaller/python-simpleline/archive/refs/tags/$_tag.tar.gz")
sha256sums=('b58cfb3ac4166b8bbbd1d3789713dc364c50509a513ead7be66a237a5b3affa3')
_srcdir="python-simpleline-$_tag"
package() {
cd "$srcdir/$_srcdir"
local sitedir
sitedir=$(python -c 'import site; print(site.getsitepackages()[0])')
install -d "$pkgdir$sitedir"
cp -a simpleline "$pkgdir$sitedir/simpleline"
# -d : grave le chemin d'exécution réel dans les .pyc (pas le $pkgdir de build).
python -m compileall -q -d "$sitedir/simpleline" "$pkgdir$sitedir/simpleline" || true
install -Dm644 LICENSE.md "$pkgdir/usr/share/licenses/$pkgname/LICENSE.md"
}
@@ -0,0 +1,898 @@
format = 2
pkgname = python-simpleline
pkgbase = python-simpleline
pkgver = 1.8-1
pkgarch = any
pkgbuild_sha256sum = 7502d64c9ab7b8658cf85b6919f95cec8bbdc1febca98f91b01de326450080ea
packager = Unknown Packager
builddate = 1781307804
builddir = /home/nocode/Files/FWS-ISO/pkgbuilds/python-simpleline
startdir = /home/nocode/Files/FWS-ISO/pkgbuilds/python-simpleline
buildtool = makepkg
buildtoolver = 7.1.0
buildenv = !distcc
buildenv = color
buildenv = !ccache
buildenv = check
buildenv = !sign
options = strip
options = docs
options = !libtool
options = !staticlibs
options = emptydirs
options = zipman
options = purge
options = debug
options = lto
installed = aardvark-dns-1.17.1-1-x86_64
installed = abseil-cpp-20260107.1-1-x86_64
installed = accountsservice-26.13.3-1-x86_64
installed = acl-2.3.2-2-x86_64
installed = adwaita-cursors-50.0-1-any
installed = adwaita-fonts-50.0-1-any
installed = adwaita-icon-theme-50.0-1-any
installed = adwaita-icon-theme-legacy-46.2-3-any
installed = alsa-card-profiles-1:1.6.6-1-x86_64
installed = alsa-lib-1.2.16-1-x86_64
installed = alsa-plugins-1:1.2.12-5-x86_64
installed = alsa-topology-conf-1.2.5.1-4-any
installed = alsa-ucm-conf-1.2.16-2-any
installed = aom-3.14.1-1-x86_64
installed = appstream-1.1.2-1-x86_64
installed = aquamarine-0.12.1-1-x86_64
installed = arch-install-scripts-31-1-any
installed = archiso-88-1-any
installed = archlinux-keyring-20260420-1-any
installed = argon2-20190702-6-x86_64
installed = at-spi2-core-2.60.4-1-x86_64
installed = atkmm-2.28.5-2-x86_64
installed = attr-2.5.2-2-x86_64
installed = audit-4.1.4-2-x86_64
installed = autoconf-2.73-1-any
installed = automake-1.18.1-1-any
installed = avahi-1:0.9rc4-1-x86_64
installed = base-3-3-any
installed = base-devel-1-2-any
installed = bash-5.3.15-1-x86_64
installed = bemoji-0.4.0-1-any
installed = binutils-2.46+r70+g155188ea10a7-1-x86_64
installed = bison-3.8.2-8-x86_64
installed = blas-3.12.1-2-x86_64
installed = bluez-libs-5.86-6-x86_64
installed = boost-libs-1.91.0-1-x86_64
installed = brightnessctl-0.5.1-3-x86_64
installed = brotli-1.2.0-1-x86_64
installed = bubblewrap-0.11.2-1-x86_64
installed = bzip2-1.0.8-6-x86_64
installed = c-ares-1.34.6-1-x86_64
installed = ca-certificates-20240618-1-any
installed = ca-certificates-mozilla-3.124-1-x86_64
installed = ca-certificates-utils-20240618-1-any
installed = cabextract-1.11-2-x86_64
installed = cairo-1.18.4-1-x86_64
installed = cairomm-1.14.6-1-x86_64
installed = cairomm-1.16-1.18.1-1-x86_64
installed = catatonit-0.2.1-2-x86_64
installed = cblas-3.12.1-2-x86_64
installed = cliphist-1:0.7.0-2-x86_64
installed = clucene-2.3.3.4-17-x86_64
installed = cmake-4.3.3-1-x86_64
installed = compiler-rt-22.1.6-1-x86_64
installed = composefs-1.0.8-1-x86_64
installed = conmon-1:2.2.1-1-x86_64
installed = containers-common-1:0.67.1-3-any
installed = coreutils-9.11-1-x86_64
installed = cpio-2.15-3-x86_64
installed = cppdap-1.58.0-3-x86_64
installed = cracklib-2.10.3-1-x86_64
installed = criu-4.2-3-x86_64
installed = crun-1.28-1-x86_64
installed = cryptsetup-2.8.6-1-x86_64
installed = curl-8.20.0-7-x86_64
installed = dav1d-1.5.3-1-x86_64
installed = db5.3-5.3.28-7-x86_64
installed = dbus-1.16.2-1-x86_64
installed = dbus-broker-37-3-x86_64
installed = dbus-broker-units-37-3-x86_64
installed = dbus-units-37-3-x86_64
installed = dconf-0.49.0-1-x86_64
installed = debugedit-5.3-1-x86_64
installed = default-cursors-3-1-any
installed = desktop-file-utils-0.28-1-x86_64
installed = device-mapper-2.03.41-1-x86_64
installed = diffutils-3.12-2-x86_64
installed = discord-1:1.0.142-1-x86_64
installed = dkms-3.4.1-1-any
installed = dos2unix-7.5.6-1-x86_64
installed = dosfstools-4.2-5-x86_64
installed = double-conversion-3.4.0-1-x86_64
installed = duktape-2.7.0-7-x86_64
installed = e2fsprogs-1.47.4-1-x86_64
installed = efibootmgr-18-4-x86_64
installed = efivar-39-2-x86_64
installed = egl-gbm-1.1.3-1-x86_64
installed = egl-wayland-4:1.1.21-1-x86_64
installed = egl-wayland2-1.0.1-1-x86_64
installed = egl-x11-1.0.5-1-x86_64
installed = eglexternalplatform-1.2.1-1-any
installed = electron41-41.7.2-1-x86_64
installed = elfutils-0.195-1-x86_64
installed = erofs-utils-1.9.1-1-x86_64
installed = exo-4.20.0-2-x86_64
installed = expat-2.8.1-1-x86_64
installed = extra-cmake-modules-6.26.0-1-any
installed = fakeroot-1:1.37.2-1-x86_64
installed = fcft-3.3.3-1-x86_64
installed = ffmpeg-2:8.1.1-2-x86_64
installed = fftw-3.3.11-1-x86_64
installed = file-5.48-1-x86_64
installed = filesystem-2025.10.12-1-any
installed = findutils-4.10.0-3-x86_64
installed = firefox-151.0.4-1-x86_64
installed = flac-1.5.0-1-x86_64
installed = flatpak-1:1.18.0-1-x86_64
installed = flex-2.6.4-6-x86_64
installed = fmt-12.1.0-2-x86_64
installed = fontconfig-2:2.18.1-1-x86_64
installed = freerdp-2:3.26.0-1-x86_64
installed = freetype2-2.14.3-1-x86_64
installed = fribidi-1.0.16-2-x86_64
installed = fuse-common-3.18.2-1-x86_64
installed = fuse2-2.9.9-5-x86_64
installed = fuse3-3.18.2-1-x86_64
installed = fuzzel-1.14.1-1-x86_64
installed = gawk-5.4.0-1-x86_64
installed = gc-8.2.12-1-x86_64
installed = gcc-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = gcr-3.41.2-2-x86_64
installed = gcr-4-4.4.0.1-1-x86_64
installed = gdb-17.2-1-x86_64
installed = gdb-common-17.2-1-x86_64
installed = gdbm-1.26-2-x86_64
installed = gdk-pixbuf2-2.44.6-2-x86_64
installed = gettext-1.0-2-x86_64
installed = giflib-6.1.3-1-x86_64
installed = git-2.54.0-1-x86_64
installed = glib-networking-1:2.80.1-1-x86_64
installed = glib2-2.88.1-1-x86_64
installed = glibc-2.43+r22+g8362e8ce10b2-2-x86_64
installed = glibmm-2.66.8-2-x86_64
installed = glibmm-2.68-2.88.0-2-x86_64
installed = glslang-1:1.4.350.0-1-x86_64
installed = glycin-2.1.1-1-x86_64
installed = gmp-6.3.0-3-x86_64
installed = gnome-themes-extra-1:3.28-1-any
installed = gnulib-l10n-20241231-1-any
installed = gnupg-2.4.9-1-x86_64
installed = gnutls-3.8.13-2-x86_64
installed = go-2:1.26.4-1-x86_64
installed = gobject-introspection-1.86.0-2-x86_64
installed = gobject-introspection-runtime-1.86.0-2-x86_64
installed = gparted-1.8.1-2-x86_64
installed = gperftools-2.18.1-1-x86_64
installed = gpgme-2.1.0-1-x86_64
installed = gpgmepp-2.1.0-1-x86_64
installed = gpm-1.20.7.r38.ge82d1a6-6-x86_64
installed = gpsd-3.27.5-1-x86_64
installed = gptfdisk-1.0.10-2-x86_64
installed = graphene-1.10.8-2-x86_64
installed = graphite-1:1.3.15-1-x86_64
installed = grep-3.12-2-x86_64
installed = grim-1.5.0-2-x86_64
installed = groff-1.24.1-1-x86_64
installed = grub-2:2.14-1-x86_64
installed = gsettings-desktop-schemas-50.1-1-any
installed = gsettings-system-schemas-50.1-1-any
installed = gsm-1.0.24-1-x86_64
installed = gssdp-1.6.5-1-x86_64
installed = gst-plugins-bad-libs-1.28.3-1-x86_64
installed = gst-plugins-base-libs-1.28.3-1-x86_64
installed = gstreamer-1.28.3-1-x86_64
installed = gtest-1.17.0-2-x86_64
installed = gtk-layer-shell-0.10.1-1-x86_64
installed = gtk-update-icon-cache-1:4.22.4-1-x86_64
installed = gtk3-1:3.24.52-1-x86_64
installed = gtk4-1:4.22.4-1-x86_64
installed = gtkmm-4.0-4.22.0-2-x86_64
installed = gtkmm3-3.24.11-1-x86_64
installed = guile-3.0.11-1-x86_64
installed = gupnp-1:1.6.10-1-x86_64
installed = gupnp-igd-1.6.0-2-x86_64
installed = gzip-1.14-2-x86_64
installed = harfbuzz-14.2.1-1-x86_64
installed = harfbuzz-icu-14.2.1-1-x86_64
installed = hicolor-icon-theme-0.18-1-any
installed = hidapi-0.15.0-1-x86_64
installed = highway-1.4.0-1-x86_64
installed = hunspell-1.7.3-1-x86_64
installed = hwdata-0.408-1-any
installed = hwloc-2.13.0-1-x86_64
installed = hyphen-2.8.9-1-x86_64
installed = hyprcursor-0.1.13-6-x86_64
installed = hyprgraphics-0.5.1-2-x86_64
installed = hypridle-0.1.7-9-x86_64
installed = hyprland-0.55.4-1-x86_64
installed = hyprland-guiutils-0.2.1-6-x86_64
installed = hyprland-qt-support-0.1.0-12-x86_64
installed = hyprlang-0.6.8-4-x86_64
installed = hyprlock-0.9.5-4-x86_64
installed = hyprpaper-0.8.4-2-x86_64
installed = hyprpicker-0.4.7-1-x86_64
installed = hyprpolkitagent-0.1.3-7-x86_64
installed = hyprshot-1.3.0-4-any
installed = hyprtoolkit-0.5.4-2-x86_64
installed = hyprutils-0.13.1-1-x86_64
installed = hyprwayland-scanner-0.4.6-1-x86_64
installed = hyprwire-0.3.1-2-x86_64
installed = iana-etc-20260530-1-any
installed = icu-78.3-1-x86_64
installed = imagemagick-7.1.2.25-1-x86_64
installed = iniparser-4.2.6-2-x86_64
installed = iproute2-7.0.0-1-x86_64
installed = iptables-1:1.8.13-1-x86_64
installed = iputils-20250605-1-x86_64
installed = iso-codes-4.20.1-1-any
installed = jack2-1.9.22-2-x86_64
installed = jansson-2.15.0-1-x86_64
installed = jbigkit-2.1-8-x86_64
installed = jq-1.8.1-3-x86_64
installed = json-c-0.18-2-x86_64
installed = json-glib-1.10.8-1-x86_64
installed = jsoncpp-1.9.6-3-x86_64
installed = kbd-2.10.0-1-x86_64
installed = kcoreaddons-6.26.0-1-x86_64
installed = keyutils-1.6.3-4-x86_64
installed = ki18n-6.26.0-1-x86_64
installed = kitty-0.47.1-1-x86_64
installed = kitty-shell-integration-0.47.1-1-x86_64
installed = kitty-terminfo-0.47.1-1-x86_64
installed = kmod-34.2-1-x86_64
installed = kpmcore-26.04.2-1-x86_64
installed = krb5-1.22.2-1-x86_64
installed = kwidgetsaddons-6.26.0-1-x86_64
installed = l-smash-2.14.5-4-x86_64
installed = lame-3.101.r6531-1-x86_64
installed = lapack-3.12.1-2-x86_64
installed = lcms2-2.19.1-1-x86_64
installed = leancrypto-1.7.2-1-x86_64
installed = lib32-alsa-lib-1.2.16-1-x86_64
installed = lib32-alsa-plugins-1.2.12-1-x86_64
installed = lib32-brotli-1.1.0-1-x86_64
installed = lib32-bzip2-1.0.8-4-x86_64
installed = lib32-curl-8.20.0-7-x86_64
installed = lib32-dbus-1.16.2-1-x86_64
installed = lib32-e2fsprogs-1.47.4-1-x86_64
installed = lib32-expat-2.8.1-1-x86_64
installed = lib32-fontconfig-2:2.18.1-1-x86_64
installed = lib32-freetype2-2.14.3-1-x86_64
installed = lib32-gcc-libs-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = lib32-glib2-2.88.1-1-x86_64
installed = lib32-glibc-2.43+r22+g8362e8ce10b2-2-x86_64
installed = lib32-gmp-6.3.0-2-x86_64
installed = lib32-gnutls-3.8.13-3-x86_64
installed = lib32-icu-78.3-1-x86_64
installed = lib32-json-c-0.18-2-x86_64
installed = lib32-keyutils-1.6.3-4-x86_64
installed = lib32-krb5-1.22.2-1-x86_64
installed = lib32-libdrm-2.4.134-1-x86_64
installed = lib32-libelf-0.195-1-x86_64
installed = lib32-libffi-3.5.2-1-x86_64
installed = lib32-libgcrypt-1.12.2-1-x86_64
installed = lib32-libglvnd-1.7.0-1-x86_64
installed = lib32-libgpg-error-1.61-1-x86_64
installed = lib32-libidn2-2.3.8-1-x86_64
installed = lib32-libldap-2.6.13-1-x86_64
installed = lib32-libnghttp2-1.69.0-1-x86_64
installed = lib32-libnghttp3-1.16.0-1-x86_64
installed = lib32-libngtcp2-1.23.0-1-x86_64
installed = lib32-libnm-1.56.1-1-x86_64
installed = lib32-libpciaccess-0.19-1-x86_64
installed = lib32-libpipewire-1:1.6.6-1-x86_64
installed = lib32-libpng-1.6.58-1-x86_64
installed = lib32-libpsl-0.21.5-1-x86_64
installed = lib32-libssh2-1.11.1-1-x86_64
installed = lib32-libtasn1-4.21.0-1-x86_64
installed = lib32-libunistring-1.4.2-1-x86_64
installed = lib32-libva-2.22.0-1-x86_64
installed = lib32-libx11-1.8.13-1-x86_64
installed = lib32-libxau-1.0.12-1-x86_64
installed = lib32-libxcb-1.17.0-1-x86_64
installed = lib32-libxcrypt-4.5.2-1-x86_64
installed = lib32-libxcrypt-compat-4.5.2-1-x86_64
installed = lib32-libxdmcp-1.1.5-1-x86_64
installed = lib32-libxext-1.3.7-1-x86_64
installed = lib32-libxfixes-6.0.1-2-x86_64
installed = lib32-libxinerama-1.1.6-1-x86_64
installed = lib32-libxml2-2.15.3-1-x86_64
installed = lib32-libxshmfence-1.3.3-1-x86_64
installed = lib32-libxss-1.2.5-1-x86_64
installed = lib32-libxxf86vm-1.1.5-2-x86_64
installed = lib32-llvm-libs-1:22.1.6-1-x86_64
installed = lib32-lm_sensors-1:3.6.2-2-x86_64
installed = lib32-mesa-1:26.1.2-1-x86_64
installed = lib32-ncurses-6.6-2-x86_64
installed = lib32-nettle-4.0-2-x86_64
installed = lib32-nspr-4.39-1-x86_64
installed = lib32-nss-3.124-1-x86_64
installed = lib32-nvidia-utils-610.43.02-1-x86_64
installed = lib32-openssl-1:3.6.3-1-x86_64
installed = lib32-p11-kit-0.26.2-1-x86_64
installed = lib32-pcre2-10.47-1-x86_64
installed = lib32-pipewire-1:1.6.6-1-x86_64
installed = lib32-spirv-tools-1:1.4.350.0-1-x86_64
installed = lib32-sqlite-3.53.2-1-x86_64
installed = lib32-systemd-260.2-1-x86_64
installed = lib32-util-linux-2.42.1-1-x86_64
installed = lib32-vulkan-icd-loader-1.4.350.0-1-x86_64
installed = lib32-wayland-1.25.0-1-x86_64
installed = lib32-xz-5.8.3-1-x86_64
installed = lib32-zlib-1.3.2-1-x86_64
installed = lib32-zstd-1.5.7-2-x86_64
installed = libabw-0.1.4-1-x86_64
installed = libadwaita-1:1.9.1-1-x86_64
installed = libaio-0.3.113-4-x86_64
installed = libappindicator-12.10.1-1-x86_64
installed = libarchive-3.8.7-1-x86_64
installed = libasan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libass-0.17.4-2-x86_64
installed = libassuan-3.0.0-1-x86_64
installed = libasyncns-1:0.8+r3+g68cd5af-3-x86_64
installed = libatomic-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libatomic_ops-7.8.2-2-x86_64
installed = libavc1394-0.5.4-7-x86_64
installed = libavif-1.4.2-1-x86_64
installed = libb2-0.98.1-3-x86_64
installed = libblake3-1.8.4-1-x86_64
installed = libbluray-1.4.1-1-x86_64
installed = libbpf-1.7.0-1-x86_64
installed = libbs2b-3.1.0-10-x86_64
installed = libbsd-0.12.2-2-x86_64
installed = libburn-1.5.8-1-x86_64
installed = libcanberra-1:0.30+r2+gc0620e4-6-x86_64
installed = libcap-2.78-1-x86_64
installed = libcap-ng-0.9.3-1-x86_64
installed = libcbor-0.14.0-1-x86_64
installed = libcdio-2.3.0-1-x86_64
installed = libcdio-paranoia-10.2+2.0.2-2-x86_64
installed = libcdr-0.1.9-1-x86_64
installed = libcloudproviders-0.4.0-1-x86_64
installed = libcmis-0.6.3-1-x86_64
installed = libcolord-1.4.8-1-x86_64
installed = libcups-2:2.4.19-1-x86_64
installed = libdaemon-0.14-6-x86_64
installed = libdatrie-0.2.14-1-x86_64
installed = libdbusmenu-glib-18.10.20180917-1-x86_64
installed = libdbusmenu-gtk3-18.10.20180917-1-x86_64
installed = libdecor-0.2.5-1-x86_64
installed = libdeflate-1.25-1-x86_64
installed = libdisplay-info-0.3.0-1-x86_64
installed = libdovi-3.3.2-1-x86_64
installed = libdrm-2.4.134-1-x86_64
installed = libdvdnav-7.0.0-1-x86_64
installed = libdvdread-7.0.1-1-x86_64
installed = libe-book-0.1.3-20-x86_64
installed = libebur128-1.2.6-2-x86_64
installed = libedit-20260512_3.1-1-x86_64
installed = libei-1.6.0-1-x86_64
installed = libelf-0.195-1-x86_64
installed = libepoxy-1.5.10-3-x86_64
installed = libepubgen-0.1.1-6-x86_64
installed = libetonyek-0.1.13-2-x86_64
installed = libevdev-1.13.6-1-x86_64
installed = libevent-2.1.12-5-x86_64
installed = libexif-0.6.26-1-x86_64
installed = libexttextcat-3.4.8-1-x86_64
installed = libfdk-aac-2.0.3-2-x86_64
installed = libffi-3.5.2-1-x86_64
installed = libfido2-1.17.0-1-x86_64
installed = libfontenc-1.1.9-1-x86_64
installed = libfreeaptx-0.2.2-1-x86_64
installed = libfreehand-0.1.3-1-x86_64
installed = libfyaml-0.9.6-2-x86_64
installed = libgcc-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libgcrypt-1.12.2-1-x86_64
installed = libgfortran-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libgirepository-1.86.0-2-x86_64
installed = libgit2-1:1.9.4-1-x86_64
installed = libglvnd-1.7.0-3-x86_64
installed = libgomp-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libgpg-error-1.61-1-x86_64
installed = libgtop-2.41.3-2-x86_64
installed = libgudev-238-3-x86_64
installed = libice-1.1.2-1-x86_64
installed = libidn2-2.3.8-1-x86_64
installed = libiec61883-1.2.0-9-x86_64
installed = libimobiledevice-1.4.0-2-x86_64
installed = libimobiledevice-glue-1.3.2-1-x86_64
installed = libinih-62-2-x86_64
installed = libinput-1.31.3-1-x86_64
installed = libisl-0.27-1-x86_64
installed = libisoburn-1.5.8.2-1-x86_64
installed = libisofs-1.5.8.2-1-x86_64
installed = libixion-0.20.0-7-x86_64
installed = libjpeg-turbo-3.1.4.1-1-x86_64
installed = libjxl-0.11.2-2-x86_64
installed = libksba-1.8.0-1-x86_64
installed = liblangtag-0.6.8-1-x86_64
installed = liblc3-1.1.3-2-x86_64
installed = libldac-2.0.2.3-3-x86_64
installed = libldap-2.6.13-1-x86_64
installed = liblqr-0.4.3-1-x86_64
installed = liblsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libmakepkg-dropins-20-1-any
installed = libmalcontent-0.14.0-4-x86_64
installed = libmd-1.2.0-1-x86_64
installed = libmm-glib-1.24.2-1-x86_64
installed = libmnl-1.0.5-2-x86_64
installed = libmodplug-0.8.9.0-7-x86_64
installed = libmpc-1.4.1-1-x86_64
installed = libmpdclient-2.23-1-x86_64
installed = libmspub-0.1.5-1-x86_64
installed = libmwaw-0.3.22-4-x86_64
installed = libmysofa-1.3.4-1-x86_64
installed = libndp-1.9-1-x86_64
installed = libnet-2:1.3-2-x86_64
installed = libnetfilter_conntrack-1.1.1-1-x86_64
installed = libnewt-0.52.25-2-x86_64
installed = libnfnetlink-1.0.2-2-x86_64
installed = libnftnl-1.3.1-1-x86_64
installed = libnghttp2-1.69.0-1-x86_64
installed = libnghttp3-1.16.0-1-x86_64
installed = libngtcp2-1.23.0-1-x86_64
installed = libnice-0.1.23-1-x86_64
installed = libnl-3.12.0-1-x86_64
installed = libnm-1.56.1-1-x86_64
installed = libnma-1.10.6-3-x86_64
installed = libnma-common-1.10.6-3-x86_64
installed = libnotify-0.8.8-1-x86_64
installed = libnsl-2.0.1-2-x86_64
installed = libnumbertext-1.0.11-3-x86_64
installed = libobjc-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libodfgen-0.1.8-5-x86_64
installed = libogg-1.3.6-1-x86_64
installed = libopenmpt-0.8.7-1-x86_64
installed = libopenrazer-0.4.0-1-x86_64
installed = liborcus-0.21.0-6-x86_64
installed = libp11-kit-0.26.2-1-x86_64
installed = libpagemaker-0.0.4-5-x86_64
installed = libpcap-1.10.6-1-x86_64
installed = libpciaccess-0.19-1-x86_64
installed = libpgm-5.3.128-4-x86_64
installed = libpipewire-1:1.6.6-1-x86_64
installed = libplacebo-7.360.1-2-x86_64
installed = libplist-2.7.0-3-x86_64
installed = libpng-1.6.58-1-x86_64
installed = libproxy-0.5.12-1-x86_64
installed = libpsl-0.21.5-2-x86_64
installed = libpulse-17.0+r98+gb096704c0-1-x86_64
installed = libpwquality-1.4.5-7-x86_64
installed = libquadmath-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libqxp-0.0.3-1-x86_64
installed = libraqm-0.10.5-1-x86_64
installed = libraw1394-2.1.2-4-x86_64
installed = libreoffice-fresh-26.2.4-1-x86_64
installed = librevenge-0.0.5-4-x86_64
installed = librsvg-2:2.62.3-1-x86_64
installed = librsync-1:2.3.4-2-x86_64
installed = libsamplerate-0.2.2-3-x86_64
installed = libsasl-2.1.28-5-x86_64
installed = libseccomp-2.6.0-1-x86_64
installed = libsecret-0.21.7-1-x86_64
installed = libsigc++-2.12.2-1-x86_64
installed = libsigc++-3.0-3.8.1-1-x86_64
installed = libsixel-1.10.5-1-x86_64
installed = libsm-1.2.6-1-x86_64
installed = libsndfile-1.2.2-4-x86_64
installed = libsodium-1.0.22-1-x86_64
installed = libsoup3-3.6.6-2-x86_64
installed = libsoxr-0.1.3-4-x86_64
installed = libssh-0.12.0-1-x86_64
installed = libssh2-1.11.1-1-x86_64
installed = libstaroffice-0.0.7-5-x86_64
installed = libstdc++-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libstemmer-3.1.1-1-x86_64
installed = libsysprof-capture-50.0-2-x86_64
installed = libtasn1-4.21.0-1-x86_64
installed = libtatsu-1.0.5-1-x86_64
installed = libteam-1.32-3-x86_64
installed = libthai-0.1.30-1-x86_64
installed = libtheora-1.2.0-1-x86_64
installed = libtiff-4.7.1-2-x86_64
installed = libtirpc-1.3.7-1-x86_64
installed = libtommath-1.3.0-2-x86_64
installed = libtool-2.6.1-1-x86_64
installed = libtsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libubsan-16.1.1+r12+g301eb08fa2c5-1-x86_64
installed = libunibreak-7.0-1-x86_64
installed = libunistring-1.4.2-1-x86_64
installed = libunwind-1.8.2-1-x86_64
installed = liburing-2.14-1-x86_64
installed = libusb-1.0.30-1-x86_64
installed = libusbmuxd-2.1.1-2-x86_64
installed = libutf8proc-2.11.3-1-x86_64
installed = libuv-1.52.1-1-x86_64
installed = libva-2.23.0-1-x86_64
installed = libva-nvidia-driver-0.0.17-1-x86_64
installed = libvdpau-1.5-4-x86_64
installed = libverto-0.3.2-6-x86_64
installed = libvisio-0.1.11-1-x86_64
installed = libvorbis-1.3.7-4-x86_64
installed = libvpl-2.16.0-2-x86_64
installed = libvpx-1.16.0-3-x86_64
installed = libwacom-2.19.0-1-x86_64
installed = libwebp-1.6.0-2-x86_64
installed = libwireplumber-0.5.14-1-x86_64
installed = libwpd-0.10.3-6-x86_64
installed = libwps-0.4.14-4-x86_64
installed = libx11-1.8.13-1-x86_64
installed = libxau-1.0.12-1-x86_64
installed = libxcb-1.17.0-1-x86_64
installed = libxcomposite-0.4.7-1-x86_64
installed = libxcrypt-4.5.2-1-x86_64
installed = libxcrypt-compat-4.5.2-1-x86_64
installed = libxcursor-1.2.3-1-x86_64
installed = libxcvt-0.1.3-1-x86_64
installed = libxdamage-1.1.7-1-x86_64
installed = libxdmcp-1.1.5-2-x86_64
installed = libxext-1.3.7-1-x86_64
installed = libxfce4ui-4.20.2-1-x86_64
installed = libxfce4util-4.20.1-1-x86_64
installed = libxfixes-6.0.2-1-x86_64
installed = libxfont2-2.0.7-1-x86_64
installed = libxft-2.3.9-1-x86_64
installed = libxi-1.8.3-1-x86_64
installed = libxinerama-1.1.6-1-x86_64
installed = libxkbcommon-1.13.2-1-x86_64
installed = libxkbcommon-x11-1.13.2-1-x86_64
installed = libxkbfile-1.2.0-1-x86_64
installed = libxml2-2.15.3-1-x86_64
installed = libxml2-legacy-2.13.9-2-x86_64
installed = libxmlb-0.3.27-1-x86_64
installed = libxmp-4.7.0-1-x86_64
installed = libxmu-1.3.1-1-x86_64
installed = libxpresent-1.0.2-1-x86_64
installed = libxrandr-1.5.5-1-x86_64
installed = libxrender-0.9.12-1-x86_64
installed = libxshmfence-1.3.3-1-x86_64
installed = libxslt-1.1.45-2-x86_64
installed = libxss-1.2.5-1-x86_64
installed = libxt-1.3.1-1-x86_64
installed = libxtst-1.2.5-1-x86_64
installed = libxv-1.0.13-1-x86_64
installed = libxxf86vm-1.1.7-1-x86_64
installed = libyaml-0.2.5-3-x86_64
installed = libyuv-r2426+464c51a03-1-x86_64
installed = libzip-1.11.4-1-x86_64
installed = libzmf-0.0.2-20-x86_64
installed = licenses-20240728-1-any
installed = lilv-0.26.4-1-x86_64
installed = linux-7.0.11.arch1-1-x86_64
installed = linux-api-headers-7.0-1-x86_64
installed = linux-firmware-20260519-1-any
installed = linux-firmware-amdgpu-20260519-1-any
installed = linux-firmware-atheros-20260519-1-any
installed = linux-firmware-broadcom-20260519-1-any
installed = linux-firmware-cirrus-20260519-1-any
installed = linux-firmware-intel-20260519-1-any
installed = linux-firmware-mediatek-20260519-1-any
installed = linux-firmware-nvidia-20260519-1-any
installed = linux-firmware-other-20260519-1-any
installed = linux-firmware-radeon-20260519-1-any
installed = linux-firmware-realtek-20260519-1-any
installed = linux-firmware-whence-20260519-1-any
installed = linux-headers-7.0.11.arch1-1-x86_64
installed = lld-22.1.6-1-x86_64
installed = llhttp-9.3.1-1-x86_64
installed = llvm-libs-22.1.6-1-x86_64
installed = lm_sensors-1:3.6.2-1-x86_64
installed = lmdb-0.9.35-1-x86_64
installed = lpsolve-5.5.2.14-1-x86_64
installed = lsb-release-2.0.r55.a25a4fc-1-any
installed = lsof-4.99.6-1-x86_64
installed = lua-5.5.0-2-x86_64
installed = lua54-5.4.8-6-x86_64
installed = luajit-2.1.1780076327+b925b3e-1-x86_64
installed = lug-helper-1:4.13-1-any
installed = lv2-1.18.10-2-x86_64
installed = lz4-1:1.10.0-2-x86_64
installed = lzo-2.10-5-x86_64
installed = m4-1.4.21-2-x86_64
installed = mailcap-2.1.54-2-any
installed = make-4.4.1-3-x86_64
installed = mako-1.11.0-1-x86_64
installed = md4c-0.5.3-1-x86_64
installed = mesa-1:26.1.2-1-x86_64
installed = meson-1.11.1-3-any
installed = mingw-w64-binutils-2.46.0-1-x86_64
installed = mingw-w64-crt-14.0.0-1-any
installed = mingw-w64-gcc-16.1.0-1-x86_64
installed = mingw-w64-headers-14.0.0-1-any
installed = mingw-w64-winpthreads-14.0.0-1-any
installed = minizip-1:1.3.2-3-x86_64
installed = mkinitcpio-41-4-any
installed = mkinitcpio-busybox-1.36.1-1-x86_64
installed = mobile-broadband-provider-info-20251101-1-any
installed = mpdecimal-4.0.1-3-x86_64
installed = mpfr-4.2.2-1-x86_64
installed = mpg123-1.33.5-1-x86_64
installed = mpv-1:0.41.0-3-x86_64
installed = mpvpaper-1.8-1-x86_64
installed = mtdev-1.1.7-1-x86_64
installed = mtools-1:4.0.49-1-x86_64
installed = mujs-1.3.9-1-x86_64
installed = muparser-2.3.5-2-x86_64
installed = nano-9.0-1-x86_64
installed = ncurses-6.6-2-x86_64
installed = neon-0.37.1-1-x86_64
installed = netavark-1.17.2-2-x86_64
installed = nettle-4.0-1-x86_64
installed = network-manager-applet-1.36.0-2-x86_64
installed = networkmanager-1.56.1-1-x86_64
installed = nftables-1:1.1.6-3-x86_64
installed = ninja-1.13.2-3-x86_64
installed = nm-connection-editor-1.36.0-2-x86_64
installed = noto-fonts-1:2026.06.01-1-any
installed = noto-fonts-cjk-20240730-1-any
installed = noto-fonts-emoji-1:2.051-1-any
installed = noto-fonts-extra-1:2026.06.01-1-any
installed = npth-1.8-1-x86_64
installed = nspr-4.39-1-x86_64
installed = nss-3.124-1-x86_64
installed = nvidia-open-610.43.02-2-x86_64
installed = nvidia-utils-610.43.02-2-x86_64
installed = ocl-icd-2.3.4-1-x86_64
installed = onetbb-2023.0.0-1-x86_64
installed = oniguruma-6.9.10-1-x86_64
installed = openal-1.25.2-1-x86_64
installed = opencore-amr-0.1.6-2-x86_64
installed = openjpeg2-2.5.4-1-x86_64
installed = openrazer-daemon-3.12.2-2-any
installed = openrazer-driver-dkms-3.12.2-2-any
installed = openssh-10.3p1-1-x86_64
installed = openssl-3.6.3-1-x86_64
installed = opus-1.6.1-1-x86_64
installed = opusfile-0.12-4-x86_64
installed = orc-0.4.42-1-x86_64
installed = os-prober-1.84-1-x86_64
installed = ostree-2025.7-3-x86_64
installed = p11-kit-0.26.2-1-x86_64
installed = pacman-7.1.0.r9.g54d9411-2-x86_64
installed = pacman-mirrorlist-20260610-1-any
installed = pahole-1:1.31-2-x86_64
installed = pam-1.7.2-2-x86_64
installed = pambase-20250719-1-any
installed = pango-1:1.57.1-1-x86_64
installed = pangomm-2.46.4-3-x86_64
installed = pangomm-2.48-2.56.1-2-x86_64
installed = papirus-icon-theme-20250501-1-any
installed = parted-3.7-1-x86_64
installed = paru-2.1.0-2-x86_64
installed = paru-debug-2.1.0-2-x86_64
installed = passt-2026_05_26.038c51e-1-x86_64
installed = patch-2.8-1-x86_64
installed = pavucontrol-1:6.2-1-x86_64
installed = pciutils-3.15.0-1-x86_64
installed = pcre-8.45-4-x86_64
installed = pcre2-10.47-1-x86_64
installed = pcsclite-2.5.1-1-x86_64
installed = perl-5.42.2-1-x86_64
installed = perl-error-0.17030-3-any
installed = perl-mailtools-2.22-3-any
installed = perl-timedate-2.35-1-any
installed = pinentry-1.3.2-2-x86_64
installed = pipewire-1:1.6.6-1-x86_64
installed = pipewire-audio-1:1.6.6-1-x86_64
installed = pipewire-pulse-1:1.6.6-1-x86_64
installed = pixman-0.46.4-1-x86_64
installed = pkgconf-2.5.1-1-x86_64
installed = playerctl-2.4.1-5-x86_64
installed = podman-5.8.3-1-x86_64
installed = podman-compose-1.6.0-1-any
installed = podman-desktop-1.27.2-2-x86_64
installed = polkit-127-3-x86_64
installed = polkit-qt6-0.201.1-1-x86_64
installed = poppler-26.05.0-1-x86_64
installed = popt-1.19-2-x86_64
installed = portaudio-1:19.7.0-4-x86_64
installed = portmidi-1:2.0.8-1-x86_64
installed = pps-tools-1.0.3-2-x86_64
installed = procps-ng-4.0.6-1-x86_64
installed = protobuf-35.0-1-x86_64
installed = protobuf-c-1.5.2-10-x86_64
installed = psmisc-23.7-2-x86_64
installed = pugixml-1.15-3-x86_64
installed = python-3.14.5-1-x86_64
installed = python-attrs-26.1.0-1-any
installed = python-autocommand-2.2.2-9-any
installed = python-cffi-2.0.0-2-x86_64
installed = python-click-8.3.3-1-any
installed = python-cryptography-48.0.1-1-x86_64
installed = python-daemonize-2.5.0-10-any
installed = python-dbus-1.4.0-2-x86_64
installed = python-dotenv-1.2.2-1-any
installed = python-filelock-3.29.0-1-any
installed = python-gobject-3.56.3-1-x86_64
installed = python-jaraco.collections-5.1.0-3-any
installed = python-jaraco.context-6.1.2-1-any
installed = python-jaraco.functools-4.1.0-3-any
installed = python-jaraco.text-4.0.0-4-any
installed = python-mako-1.3.11-1-any
installed = python-markdown-3.10.2-1-any
installed = python-markupsafe-3.0.3-1-x86_64
installed = python-more-itertools-11.1.0-1-any
installed = python-numpy-2.4.6-1-x86_64
installed = python-openrazer-3.12.2-2-any
installed = python-packaging-26.2-1-any
installed = python-pip-26.1.2-1-any
installed = python-pkg_resources-81.0.0-1-any
installed = python-platformdirs-4.10.0-1-any
installed = python-protobuf-35.0-1-x86_64
installed = python-pycparser-3.00-1-any
installed = python-pygame-2.6.1-5-x86_64
installed = python-pyudev-0.24.4-1-any
installed = python-setproctitle-1.3.7-2-x86_64
installed = python-setuptools-1:82.0.1-1-any
installed = python-tqdm-4.68.1-1-any
installed = python-typing_extensions-4.15.0-3-any
installed = python-wheel-0.47.0-1-any
installed = python-yaml-6.0.3-2-x86_64
installed = qt5-base-5.15.19+kde+r96-1-x86_64
installed = qt5-declarative-5.15.19+kde+r23-1-x86_64
installed = qt5-graphicaleffects-5.15.19-1-x86_64
installed = qt5-quickcontrols2-5.15.19+kde+r5-1-x86_64
installed = qt5-svg-5.15.19+kde+r5-1-x86_64
installed = qt5-translations-5.15.19-1-any
installed = qt5-wayland-5.15.19+kde+r55-1-x86_64
installed = qt6-5compat-6.11.1-1-x86_64
installed = qt6-base-6.11.1-1-x86_64
installed = qt6-declarative-6.11.1-3-x86_64
installed = qt6-shadertools-6.11.1-1-x86_64
installed = qt6-svg-6.11.1-1-x86_64
installed = qt6-tools-6.11.1-1-x86_64
installed = qt6-translations-6.11.1-1-any
installed = qt6-wayland-6.11.1-1-x86_64
installed = raptor-2.0.16-9-x86_64
installed = rasqal-1:0.9.33-9-x86_64
installed = rav1e-0.8.1-2-x86_64
installed = razergenie-1.3.0-1-x86_64
installed = re2-2:2025.11.05-4-x86_64
installed = readline-8.3.003-1-x86_64
installed = redland-1:1.0.17-10-x86_64
installed = reflector-2023-5-any
installed = resvg-0.47.0-2-x86_64
installed = rhash-1.4.6-1-x86_64
installed = rsync-3.4.4-1-x86_64
installed = rtkit-0.14-1-x86_64
installed = rubberband-4.0.0-2-x86_64
installed = rustup-1.29.0-2-x86_64
installed = sbc-2.2-1-x86_64
installed = scdoc-1.11.4-1-x86_64
installed = sdbus-cpp-2.3.1-1-x86_64
installed = sddm-0.21.0-7-x86_64
installed = sddm-catppuccin-git-r42.10831de-1-any
installed = sdl2-compat-2.32.70-1-x86_64
installed = sdl2_image-2.8.12-1-x86_64
installed = sdl2_mixer-2.8.2-1-x86_64
installed = sdl2_ttf-2.24.0-2-x86_64
installed = sdl3-3.4.10-1-x86_64
installed = sdl3_ttf-3.2.2-3-x86_64
installed = seatd-0.9.3-1-x86_64
installed = sed-4.10-1-x86_64
installed = serd-0.32.10-1-x86_64
installed = shaderc-2026.2-1-x86_64
installed = shadow-4.18.0-1-x86_64
installed = shared-mime-info-2.4-3-x86_64
installed = slang-2.3.3-4-x86_64
installed = slurp-1.5.0-2-x86_64
installed = smartmontools-7.5-1-x86_64
installed = snappy-1.2.2-3-x86_64
installed = sndio-1.10.0-1-x86_64
installed = sord-0.16.22-1-x86_64
installed = sound-theme-freedesktop-0.8-6-any
installed = source-highlight-3.1.9-18-x86_64
installed = spandsp-0.0.6-7-x86_64
installed = spdlog-1.17.0-2-x86_64
installed = speex-1.2.1-2-x86_64
installed = speexdsp-1.2.1-2-x86_64
installed = spirv-tools-1:1.4.350.0-1-x86_64
installed = sqlite-3.53.2-1-x86_64
installed = squashfs-tools-4.7.5-1-x86_64
installed = sratom-0.6.22-1-x86_64
installed = srt-1.5.5-1-x86_64
installed = startup-notification-0.12-9-x86_64
installed = steam-1.0.0.85-7-x86_64
installed = steam-devices-1.0.0.85-7-x86_64
installed = sudo-1.9.17.p2-2-x86_64
installed = svt-av1-4.1.0-1-x86_64
installed = systemd-260.2-2-x86_64
installed = systemd-libs-260.2-2-x86_64
installed = systemd-sysvcompat-260.2-2-x86_64
installed = tar-1.35-2-x86_64
installed = tcl-8.6.16-1-x86_64
installed = tdb-1.4.15-1-x86_64
installed = texinfo-7.3-1-x86_64
installed = thunar-4.20.8-3-x86_64
installed = tinysparql-3.11.1-1-x86_64
installed = tk-8.6.16-1-x86_64
installed = tomlplusplus-3.4.0-2-x86_64
installed = tpm2-tss-4.1.3-1-x86_64
installed = tslib-1.24-1-x86_64
installed = ttf-jetbrains-mono-nerd-3.4.0-2-any
installed = tzdata-2026b-1-x86_64
installed = uchardet-0.0.8-4-x86_64
installed = uefitool-debug-1:0.28.0-3-x86_64
installed = unzip-6.0-23-x86_64
installed = upower-1.91.2-1-x86_64
installed = usbutils-019-1-x86_64
installed = util-linux-2.42.1-1-x86_64
installed = util-linux-libs-2.42.1-1-x86_64
installed = v4l-utils-1.32.0-2-x86_64
installed = vapoursynth-76-1-x86_64
installed = vid.stab-1.1.1-2-x86_64
installed = vim-9.2.0623-1-x86_64
installed = vim-runtime-9.2.0623-1-x86_64
installed = vmaf-3.1.0-1-x86_64
installed = vmware-keymaps-1.0-3-any
installed = vmware-workstation-26H1-3-x86_64
installed = vscodium-bin-1.121.03429-1-x86_64
installed = vulkan-icd-loader-1.4.350.0-1-x86_64
installed = vulkan-tools-1.4.350.0-1-x86_64
installed = waybar-0.15.0-2-x86_64
installed = wayland-1.25.0-1-x86_64
installed = wayland-protocols-1.49-1-any
installed = webrtc-audio-processing-1-1.3-5-x86_64
installed = which-2.25-1-x86_64
installed = winboat-bin-0.9.0-1-x86_64
installed = wine-staging-11.10-1-x86_64
installed = wireplumber-0.5.14-1-x86_64
installed = wl-clipboard-1:2.3.0-1-x86_64
installed = wlogout-1.2.2-0-x86_64
installed = woff2-font-awesome-7.2.0-1-any
installed = wpa_supplicant-2:2.11-5-x86_64
installed = x264-3:0.165.r3222.b35605a-2-x86_64
installed = x265-4.1-1-x86_64
installed = xautomation-1.09-6-x86_64
installed = xcb-proto-1.17.0-4-any
installed = xcb-util-0.4.1-2-x86_64
installed = xcb-util-cursor-0.1.6-1-x86_64
installed = xcb-util-errors-1.0.1-2-x86_64
installed = xcb-util-image-0.4.1-3-x86_64
installed = xcb-util-keysyms-0.4.1-5-x86_64
installed = xcb-util-renderutil-0.3.10-2-x86_64
installed = xcb-util-wm-0.4.2-2-x86_64
installed = xdg-dbus-proxy-0.1.7-1-x86_64
installed = xdg-desktop-portal-1.22.0-1-x86_64
installed = xdg-desktop-portal-gtk-1.15.3-1-x86_64
installed = xdg-desktop-portal-hyprland-1.3.12-2-x86_64
installed = xdg-user-dirs-0.20-1-x86_64
installed = xdg-utils-1.2.1-2-any
installed = xf86-input-libinput-1.5.0-1-x86_64
installed = xfconf-4.20.0-2-x86_64
installed = xkeyboard-config-2.47-1-any
installed = xmlsec-1.3.11-1-x86_64
installed = xorg-fonts-encodings-1.1.0-2-any
installed = xorg-server-21.1.23-1-x86_64
installed = xorg-server-common-21.1.23-1-x86_64
installed = xorg-setxkbmap-1.3.5-1-x86_64
installed = xorg-xauth-1.1.5-1-x86_64
installed = xorg-xkbcomp-1.5.0-1-x86_64
installed = xorg-xprop-1.2.8-1-x86_64
installed = xorg-xrandr-1.5.4-1-x86_64
installed = xorg-xwayland-24.1.12-1-x86_64
installed = xorgproto-2025.1-1-any
installed = xvidcore-1.3.7-3-x86_64
installed = xxhash-0.8.3-1-x86_64
installed = xz-5.8.3-1-x86_64
installed = yaml-cpp-0.9.0-1-x86_64
installed = yay-12.6.0-1-x86_64
installed = yay-debug-12.6.0-1-x86_64
installed = zenity-4.2.2-1-x86_64
installed = zeromq-4.3.5-3-x86_64
installed = zimg-3.0.6-1-x86_64
installed = zint-2.16.0-2-x86_64
installed = zix-0.8.0-1-x86_64
installed = zlib-1:1.3.2-3-x86_64
installed = zlib-ng-2.3.3-1-x86_64
installed = zstd-1.5.7-3-x86_64
installed = zxing-cpp-3.0.2-1-x86_64
@@ -0,0 +1,15 @@
# Generated by makepkg 7.1.0
# using fakeroot version 1.37.2
pkgname = python-simpleline
pkgbase = python-simpleline
xdata = pkgtype=pkg
pkgver = 1.8-1
pkgdesc = Python3 text UI framework (dépendance Anaconda)
url = https://github.com/rhinstaller/python-simpleline
builddate = 1781307804
packager = Unknown Packager
size = 346552
arch = any
license = LGPL-3.0-or-later
depend = python
depend = python-gobject
@@ -0,0 +1,136 @@
# Base class for Simpleline Text UI framework.
#
# Library containing the Text UI framework.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
__all__ = ["App"]
from simpleline.logging import setup_logging
from simpleline.errors import NothingScheduledError
setup_logging()
class App():
"""This is the main class for Simpleline library.
Do not create instance of this class. Use this class as static!
The `initialize()` method must be called before use.
It is giving you access to the scheduler and event loop. You can have only one instance of this
class in your application.
To create this instance call `App.initialize()` method. This method can also be used to
reset settings in the App class to start with new event loop or scheduler.
"""
__app = None
class AppPimpl():
def __init__(self, scheduler, event_loop, configuration):
self.event_loop = event_loop
self.scheduler = scheduler
self.configuration = configuration
@classmethod
def initialize(cls, scheduler=None, event_loop=None, global_configuration=None):
"""Create app instance inside of this class.
This method can be called multiple times to reset App settings.
:param scheduler: scheduler used for rendering screens; if not specified use
`simpleline.render.screen_scheduler.ScreenScheduler`.
:type scheduler: instance of `simpleline.render.screen_scheduler.ScreenScheduler`.
:param event_loop: event loop used for asynchronous tasks;
if not specified use `simpleline.event_loop.main_loop.MainLoop`.
:type event_loop: object based on class `simpleline.event_loop.AbstractEventLoop`.
:param global_configuration: instance of the global configuration object; if not specified
use `simpleline.global_configuration.GlobalConfiguration`
:type global_configuration: object based on class
`simpleline.global_configuration.GlobalConfiguration`
"""
from simpleline.event_loop.main_loop import MainLoop # pylint: disable=import-outside-toplevel
from simpleline.render.screen_scheduler import ScreenScheduler # pylint: disable=import-outside-toplevel
from simpleline.global_configuration import GlobalConfiguration # pylint: disable=import-outside-toplevel
if event_loop is None:
event_loop = MainLoop()
if scheduler is None:
scheduler = ScreenScheduler(event_loop)
if global_configuration is None:
global_configuration = GlobalConfiguration()
cls.__app = cls.AppPimpl(scheduler, event_loop, global_configuration)
cls._post_init()
@classmethod
def _post_init(cls):
from simpleline.input.input_threading import InputThreadManager # pylint: disable=import-outside-toplevel
# FIXME: This should be done by more general way not by calling exact class here.
# Create new instance of InputThreadManager because it needs new event loop
InputThreadManager.create_new_instance()
@classmethod
def is_initialized(cls):
"""Is the App already initialized?
:returns: True if the `App.initialized()` method was called, False otherwise.
"""
if cls.__app is None:
return False
return True
@classmethod
def get_scheduler(cls):
"""Get instance of class responsible for rendering of the screen."""
return cls.__app.scheduler
@classmethod
def get_event_loop(cls):
"""Get instance of class responsible for processing asynchronous events."""
return cls.__app.event_loop
@classmethod
def get_configuration(cls):
"""Get application defaults configuration object."""
return cls.__app.configuration
@classmethod
def run(cls):
"""Run event loop.
Raise an exception if no screen is scheduled. This behavior can be changed by
`should_run_with_empty_stack` global configuration option.
This is shortcut to `App.event_loop().run()`.
:raises NothingScheduledError: when there is no screen scheduled
"""
if not cls.__app.configuration.should_run_with_empty_stack:
# Check if the screen stack is not empty
if cls.__app.scheduler.nothing_to_render:
raise NothingScheduledError("Can't run application with the empty screen stack! "
"To avoid this please see should_run_with_empty_stack "
"global configuration option.")
App.get_event_loop().run()
@@ -0,0 +1,31 @@
# Base exceptions for the Simpleline application.
#
# Base class for Simpleline Text UI framework.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# This can't be moved to __init__.py because of cyclic imports error.
#
class SimplelineError(Exception):
"""Base exception for all other exceptions."""
class NothingScheduledError(SimplelineError):
"""Exception when running the loop with no screens scheduled."""
@@ -0,0 +1,264 @@
# Abstract base class for Simpleline Event Loop.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# This class can be overridden to use any existing event loop of your program.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
import sys
from abc import ABCMeta, abstractmethod
from collections import namedtuple
from simpleline.errors import SimplelineError
from simpleline.event_loop.ticket_machine import TicketMachine
from simpleline.logging import get_simpleline_logger
log = get_simpleline_logger()
__all__ = ["AbstractEventLoop", "AbstractSignal", "ExitMainLoop"]
QuitCallback = namedtuple("QuitCallback", ["callback", "args"])
class ExitMainLoop(SimplelineError):
"""This exception ends the whole event loop."""
class AbstractEventLoop(metaclass=ABCMeta):
def __init__(self):
super().__init__()
self._handlers = {}
self._processed_signals = TicketMachine()
self._quit_callback = None
# end most inner loop politely by setting to False
self._run_loop = True
self._force_quit = False
def register_signal_handler(self, signal, callback, data=None):
"""Register a callback which will be called when message "event"
is encountered during process_events.
The callback has to accept two arguments:
- the received message in the form of (type, [arguments])
- the data registered with the handler
:param signal: Signal class we want to react on.
:type signal: Class based on the simpleline.event_loop.AbstractSignal class.
:param callback: The callback function.
:type callback: func(event_message, data)
:param data: Optional data to pass to callback.
:type data: Anything.
"""
if signal not in self._handlers:
self._handlers[signal] = []
event_handler = self._create_event_handler(callback, data)
self._handlers[signal].append(event_handler)
@abstractmethod
def register_signal_source(self, signal_source):
"""Register source of signal for actual event queue.
:param signal_source: Source for future signals.
:type signal_source: `simpleline.render.ui_screen.UIScreen`
"""
@abstractmethod
def enqueue_signal(self, signal):
"""Enqueue new event for processing.
:param signal: Signal which you want to add to the event queue for processing.
:type signal: Instance based on AbstractEvent class.
"""
log.debug("New signal %s enqueued with source %s",
signal,
signal.source.__class__.__name__)
@abstractmethod
def run(self):
"""Starts the event loop."""
log.debug("Starting main loop")
self._force_quit = False
def force_quit(self):
"""Force quit all running event loops.
Kill all loop including inner loops (modal window).
None of the Simpleline events will be processed anymore.
"""
log.debug("Force quit called. Killing all loops!")
self._force_quit = True
@abstractmethod
def execute_new_loop(self, signal):
"""Starts the new event loop and pass `signal` in it.
This is required for processing a modal screens.
:param signal: Signal passed to the new event loop.
:type signal: The `AbstractSignal` based class.
"""
log.debug("Executing inner loop")
@abstractmethod
def close_loop(self):
"""Close active event loop.
Close an event loop created by the `execute_new_loop()` method.
"""
log.debug("Closing inner loop")
@abstractmethod
def process_signals(self, return_after=None):
"""This method processes incoming async messages.
Process signals enqueued by the `self.enqueue_signal()` method. Call handlers
registered to the signals by the `self.register_signal_handler()` method.
When `return_after` is specified then wait to the point when this signal is processed.
NO warranty that this method will return immediately after the signal was processed!
Without `return_after` parameter this method will return after all queued signals
with the highest priority will be processed.
The method is NOT thread safe!
:param return_after: Wait on this signal to be processed.
:type return_after: Class of the signal.
"""
def set_quit_callback(self, callback, args=None):
"""Call this callback when event loop quits.
:param callback: Call this callback when event loops ends (application quit).
:type callback: Function with one parameter data `func(data)`.
:param args: Arguments passed to the quit callback.
:type args: Anything.
"""
self._quit_callback = QuitCallback(callback, args)
def kill_app_with_traceback(self, exception_signal, data=None):
"""Print exception and screen stack and kill the application.
:param exception_signal: ExceptionSignal encapsulating the original exception which
will be passed to the sys.excepthook method.
:type exception_signal: Instance of `simpleline.event_loop.signals.ExceptionSignal` class.
:param data: To be usable as signal handler.
:type data: Anything will be ignored.
"""
log.debug("Unhandled error in handler raised:")
sys.excepthook(*exception_signal.exception_info)
from simpleline import App # pylint: disable=import-outside-toplevel
stack_dump = App.get_scheduler().dump_stack()
print("")
print(stack_dump)
log.error(stack_dump)
log.debug("Killing application!")
sys.exit(1)
@staticmethod
def _create_event_handler(callback, data):
"""Create event handler data object and return it."""
return EventHandler(callback=callback, data=data)
def _register_wait_on_signal(self, wait_on_signal):
"""Register process waiting on signal `wait_on_signal` and return id for later checking.
ID is returned which is then used in the `self._check_if_signal_processed()` method.
:param wait_on_signal: Signal we are waiting for.
:type wait_on_signal: Class based on `simpleline.event_loop.AbstractSignal`.
"""
return self._processed_signals.take_ticket(wait_on_signal.__name__)
def _mark_signal_processed(self, signal):
"""Mark that processes waiting on this signal that they are able to go.
:param signal: Signal which was processed.
:type signal: Class based on `simpleline.event_loop.AbstractSignal`.
"""
self._processed_signals.mark_line_to_go(signal.__class__.__name__)
def _check_if_signal_processed(self, wait_on_signal, unique_id):
"""Check if the signal was processed.
:param wait_on_signal: Signal the process is waiting for.
:type wait_on_signal: Class based on `simpleline.event_loop.AbstractSignal`.
:param unique_id: Unique id returned by the `self._register_wait_on_signal()` method.
:type unique_id: int
"""
return self._processed_signals.check_ticket(wait_on_signal.__name__, unique_id)
class EventHandler():
"""Data class to save event handlers."""
def __init__(self, callback, data):
self.callback = callback
self.data = data
class AbstractSignal(metaclass=ABCMeta):
"""This class is base class for signals.
.. NOTE:
Ordering and equality is based on priority.
"""
def __init__(self, source, priority=0):
self._source = source
self._priority = priority
def __lt__(self, other):
"""Order Signal classes by priority."""
return self._priority < other.priority
def __eq__(self, other):
"""Order Signal classes by priority."""
return self._priority == other.priority
def __str__(self):
"""For easier logging."""
return self.__class__.__name__
@property
def priority(self):
"""Priority of this event.
Values less than 0 denote higher priorities. Values greater than 0 denote lower priorities.
Events from high priority sources are always processed before events from lower priority
sources.
"""
return self._priority
@property
def source(self):
"""Source which emitted this event."""
return self._source
@@ -0,0 +1,153 @@
# Default event queue for Simpleline application.
#
# This class is thread safe.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from queue import PriorityQueue
from threading import Lock
from simpleline.errors import SimplelineError
class EventQueueError(SimplelineError):
"""Main exception for `EventQueue` class.
Inherits from `simpleline.SimplelineError`.
"""
class EventQueue():
"""Class for managing signal queue.
Responsibilities of this class are:
* sorting by priority of signals
* managing sources for this event queue
* enqueuing signals
"""
def __init__(self):
self._queue = PriorityQueue()
self._contained_screens = set()
self._lock = Lock()
def empty(self):
"""Return true if Queue is empty.
:return: True if empty, False otherwise.
"""
return self._queue.empty()
def enqueue(self, signal):
"""Enqueue signal to this queue.
:param signal: Signal which should be enqueued to this queue.
:type signal: Signal class based on `simpleline.event_loop.signals.AbstractSignal`.
"""
self._queue.put(signal)
def enqueue_if_source_belongs(self, signal, source):
"""Enqueue signal to this queue if the signal source belongs to this queue.
Enqueue the `signal` only if the `source` belongs to this queue.
See the `add_source()` method.
:param signal: Signal which should be enqueued to this queue.
:type signal: Signal class based on `simpleline.event_loop.signals.AbstractSignal`.
:param source: Source of this signal.
:type source: Anything.
:return: True if the source belongs to this queue and signal was queued, False otherwise.
:rtype: bool
"""
if self.contains_source(source):
self._queue.put(signal)
return True
return False
def get(self):
"""Return enqueued signal with the highest priority.
This is FIFO implementation for the same priority.
If the queue is empty this method will wait for the input signal.
:return: Queued signal.
:rtype: Signal based on class `simpleline.event_loop.signals.AbstractSignal`.
"""
return self._queue.get()
def get_top_event_if_priority(self, priority):
"""Return top enqueued signal if priority is equal to `priority`. Otherwise `None`.
:param priority: Requested event priority.
:type priority: int
:return: Queued signal if it has requested priority. Otherwise `None`.
:rtype: Signal based on class `simpleline.event_loop.signals.AbstractSignal` or `None`.
"""
event = self._queue.get()
if event.priority == priority:
return event
self._queue.put(event)
return None
def add_source(self, signal_source):
"""Add new source of signals to this queue.
This method is mandatory for `enqueue_if_source_belongs()` method.
The same source will be added only once.
:param signal_source: Source of future signals.
:type signal_source: Anything which will emit signals in future.
"""
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
# pylint: disable=not-context-manager
with self._lock:
self._contained_screens.add(signal_source)
def remove_source(self, signal_source):
"""Remove signal source from this queue.
:param signal_source: Source of future signals.
:type signal_source: Anything.
:raise: EventQueueError"""
try:
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
# pylint: disable=not-context-manager
with self._lock:
self._contained_screens.remove(signal_source)
except KeyError as e:
raise EventQueueError("Can't remove non-existing event source!") from e
def contains_source(self, signal_source):
"""Test if `signal_source` belongs to this queue.
:param signal_source: Source of signals.
:type signal_source: Anything.
:return: True if signal source belongs to this queue.
:rtype: bool
"""
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
# pylint: disable=not-context-manager
with self._lock:
return signal_source in self._contained_screens
@@ -0,0 +1,230 @@
# Glib event queue used by Simpleline application.
#
# This class is thread safe.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from collections import namedtuple
import gi
from simpleline.event_loop import AbstractEventLoop, ExitMainLoop
from simpleline.event_loop.signals import ExceptionSignal
from simpleline.logging import get_simpleline_logger
gi.require_version("GLib", "2.0")
from gi.repository import GLib # pylint: disable=wrong-import-order, wrong-import-position
log = get_simpleline_logger()
CallbackArgs = namedtuple("CallbackArgs", ["signal", "source", "handlers"])
__all__ = ["GLibEventLoop"]
class GLibEventLoop(AbstractEventLoop):
def __init__(self):
super().__init__()
# Create first loop
loop = GLib.MainLoop()
self._event_loops = [EventLoopData(loop)]
log.debug("GLib event loop is used!")
@property
def active_main_loop(self):
"""Return GLib mainloop object."""
return self._event_loops[-1].loop
def register_signal_source(self, signal_source):
"""Register source of signal for actual event queue.
:param signal_source: Source for future signals.
:type signal_source: `simpleline.render.ui_screen.UIScreen`
"""
super().register_signal_source(signal_source)
loop_data = self._event_loops[-1]
loop_data.sources.add(signal_source)
def enqueue_signal(self, signal):
"""Enqueue new event for processing.
:param signal: signal which you want to add to the event queue for processing
:type signal: instance based on AbstractEvent class
"""
if self._force_quit:
return
super().enqueue_signal(signal)
loop_data = self._find_loop_data_for_source(signal.source)
self._register_handlers_to_loop(loop_data.loop, signal)
def _find_loop_data_for_source(self, source):
"""Find event loop belonging to this signal source."""
for loop_data in reversed(self._event_loops):
if source in loop_data.sources:
return loop_data
return self._event_loops[-1]
def _register_handlers_to_loop(self, event_loop, signal):
"""Register handlers to the event loop."""
context = event_loop.get_context()
handlers = []
if type(signal) in self._handlers: # pylint: disable=unidiomatic-typecheck
handlers = self._handlers[type(signal)]
elif isinstance(signal, ExceptionSignal):
handler_data = self._create_event_handler(self.kill_app_with_traceback, None)
handlers = [handler_data]
# GLib event source which contains handler callback
# Every source can hold only one callback
source = GLib.idle_source_new()
source.set_priority(signal.priority)
data = CallbackArgs(signal, source, handlers)
source.set_callback(self._run_handlers, data)
# attach source to the event loop
source.attach(context)
def _run_handlers(self, data):
"""Run handlers attached to this signal and clean source afterwards."""
signal = data.signal
source = data.source
handlers = data.handlers
if not self._force_quit:
try:
for handler in handlers:
handler.callback(signal, handler.data)
except ExitMainLoop:
self._quit_all_loops()
except Exception: # pylint: disable=broad-except
self.enqueue_signal(ExceptionSignal(self))
# based on GLib documentation we should clean source
# source will be removed from event loop context this way
source.destroy()
self._mark_signal_processed(signal)
def _quit_all_loops(self):
for loop_data in reversed(self._event_loops):
loop_data.loop.quit()
def run(self):
"""Starts the event loop."""
super().run()
if len(self._event_loops) != 1:
raise ValueError("Can't run event loop multiple times.")
self._event_loops[0].loop.run()
log.debug("Main loop ended. Running callback if set.")
if self._quit_callback:
cb = self._quit_callback.callback
cb(self._quit_callback.args)
def force_quit(self):
"""Force quit all running event loops.
Kill all loop including inner loops (modal window).
None of the Simpleline events will be processed anymore.
"""
super().force_quit()
self._quit_all_loops()
def execute_new_loop(self, signal):
"""Starts the new event loop and pass `signal` in it.
This is required for processing a modal screens.
:param signal: signal passed to the new event loop
:type signal: `AbstractSignal` based class
"""
super().execute_new_loop(signal)
if self._force_quit:
return
new_context = GLib.MainContext()
new_loop = GLib.MainLoop(new_context)
loop_data = EventLoopData(new_loop)
self._event_loops.append(loop_data)
self.enqueue_signal(signal)
new_loop.run()
def close_loop(self):
"""Close active event loop.
Close an event loop created by the `execute_new_loop()` method.
"""
super().close_loop()
old_loop_data = self._event_loops.pop()
old_loop_data.loop.quit()
def process_signals(self, return_after=None):
"""This method processes incoming async messages.
Process signals en-queued by the `self.enqueue_signal()` method. Call handlers registered
to the signals by the `self.register_signal_handler()` method.
When `return_after` is specified then wait to the point when this signal is processed.
NO warranty that this method will return immediately after the signal was processed!
Without `return_after` parameter this method will return after all queued signals with
the highest priority will be processed.
The method is NOT thread safe!
:param return_after: Wait on this signal to be processed.
:type return_after: Class of the signal.
"""
super().process_signals(return_after)
loop_data = self._event_loops[-1]
if return_after is not None:
ticket_id = self._register_wait_on_signal(return_after)
while not self._check_if_signal_processed(return_after, ticket_id) and \
not self._force_quit:
self._iterate_event_loop(loop_data.loop)
else:
self._iterate_event_loop(loop_data.loop)
@staticmethod
def _iterate_event_loop(event_loop):
context = event_loop.get_context()
# This is useful for tests
wait_on_timeout = False
context.iteration(wait_on_timeout)
class EventLoopData():
def __init__(self, loop):
super().__init__()
self.loop = loop
self.sources = set()
@@ -0,0 +1,247 @@
# Default event loop for Simpleline application.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from threading import Lock
from simpleline.event_loop import AbstractEventLoop, ExitMainLoop
from simpleline.event_loop.event_queue import EventQueue
from simpleline.event_loop.signals import ExceptionSignal
from simpleline.logging import get_simpleline_logger
log = get_simpleline_logger()
__all__ = ["MainLoop"]
class MainLoop(AbstractEventLoop):
"""Default main event loop for the Simpleline.
This event loop can be replaced by your event loop by implementing
`simpleline.event_loop.AbstractEventLoop` class.
"""
def __init__(self):
super().__init__()
self._active_queue = EventQueue()
self._event_queues = [self._active_queue]
self._lock = Lock()
def register_signal_source(self, signal_source):
"""Register source of signal for actual event queue.
:param signal_source: Source for future signals.
:type signal_source: `simpleline.render.ui_screen.UIScreen`.
"""
super().register_signal_source(signal_source)
self._active_queue.add_source(signal_source)
def run(self):
"""This methods starts the application.
Do not use self.mainloop() directly as run() handles all the required exceptions
needed to keep nested mainloop working.
"""
super().run()
self._run_loop = True
try:
self._mainloop()
except ExitMainLoop:
pass
log.debug("Main loop ended. Running callback if set.")
if self._quit_callback:
cb = self._quit_callback.callback
cb(self._quit_callback.args)
def force_quit(self):
"""Force quit all running event loops.
Kill all loop including inner loops (modal window).
None of the Simpleline events will be processed anymore.
"""
super().force_quit()
self._event_queues.clear()
self._run_loop = False
def execute_new_loop(self, signal):
"""Starts the new event loop and pass `signal` in it.
This is required for processing a modal screens.
:param signal: Signal passed to the new event loop.
:type signal: The `AbstractSignal` based class.
"""
super().execute_new_loop(signal)
if self._force_quit:
return
self._active_queue = EventQueue()
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
# pylint: disable=not-context-manager
with self._lock:
self._event_queues.append(self._active_queue)
self.enqueue_signal(signal)
self._mainloop()
log.debug("Inner loop is closed")
def close_loop(self):
"""Close active event loop.
Close an event loop created by the `execute_new_loop()` method.
"""
super().close_loop()
self.process_signals()
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
# pylint: disable=not-context-manager
with self._lock:
self._event_queues.pop()
try:
self._active_queue = self._event_queues[-1]
except IndexError:
log.error("No more event queues to work with!")
raise ExitMainLoop() # pylint: disable=raise-missing-from
self._run_loop = False
def enqueue_signal(self, signal):
"""Enqueue new event for processing.
Enqueue signal to the most inner queue (nearest to the active queue) where
the `signal.source` belongs.
If it belongs nowhere enqueue it to the active one.
This method is thread safe.
:param signal: Event which you want to add to the event queue for processing.
:type signal: Instance based on AbstractEvent class.
"""
if self._force_quit:
return
super().enqueue_signal(signal)
# TODO: Remove when python3-astroid 1.5.3 will be in Fedora
# pylint: disable=not-context-manager
with self._lock:
for queue in reversed(self._event_queues):
if queue.enqueue_if_source_belongs(signal, signal.source):
return
self._active_queue.enqueue(signal)
def _mainloop(self):
"""Single mainloop. Do not use directly, start the application using run()."""
# run infinite loop
# this will always wait on input processing or similar so it should not busy waiting
while self._run_loop:
self._process_signals_loop()
if not self._force_quit:
# set back to True to leave outer loop working
self._run_loop = True
def process_signals(self, return_after=None):
"""This method processes incoming async messages.
Process signals en-queued by the `self.enqueue_signal()` method. Call handlers
registered to the signals by the `self.register_signal_handler()` method.
When `return_after` is specified then wait to the point when this signal is processed.
NO warranty that this method will return immediately after the signal was processed!
Without `return_after` parameter this method will return after all queued signals
with the highest priority will be processed.
The method is NOT thread safe!
:param return_after: Wait on this signal to be processed.
:type return_after: Class of the signal.
"""
super().process_signals(return_after)
if return_after is not None:
self._process_signals_with_return(return_after)
else:
self._process_signals_iteration()
def _process_signals_with_return(self, return_after):
"""Process signals until the return_after signal was processed.
Or the loop quited.
"""
# get unique ID when waiting for the signal
unique_id = self._register_wait_on_signal(return_after)
while self._run_loop:
signal = self._active_queue.get()
# do the signal processing (call handlers)
self._process_signal(signal)
# was our signal processed if yes, return this method
if self._check_if_signal_processed(return_after, unique_id):
return
def _process_signals_iteration(self):
"""Process queued signal and then return."""
priority = None
while not self._active_queue.empty() and self._run_loop:
if priority is None:
# take first signal to find out the highest priority in queue
signal = self._active_queue.get()
priority = signal.priority
else:
# get signal with this priority only
signal = self._active_queue.get_top_event_if_priority(priority)
# Signal with this priority is not available anymore
if signal is None:
return
self._process_signal(signal)
def _process_signals_loop(self):
"""Process signal until the event loop quited."""
while self._run_loop:
signal = self._active_queue.get()
self._process_signal(signal)
def _process_signal(self, signal):
log.debug("Processing signal %s", signal)
self._mark_signal_processed(signal)
if type(signal) in self._handlers: # pylint: disable=unidiomatic-typecheck
for handler_data in self._handlers[type(signal)]:
try:
handler_data.callback(signal, handler_data.data)
except ExitMainLoop: # pylint: disable=try-except-raise
raise
except Exception: # pylint: disable=broad-except
self.enqueue_signal(ExceptionSignal(self))
elif isinstance(signal, ExceptionSignal):
self.kill_app_with_traceback(signal)
@@ -0,0 +1,110 @@
# Set of default signals used inside of widgets.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from sys import exc_info
from simpleline.event_loop import AbstractSignal
__all__ = ["ExceptionSignal", "InputReadySignal", "RenderScreenSignal", "CloseScreenSignal",
"InputReceivedSignal"]
class ExceptionSignal(AbstractSignal):
"""Emit this signal when exception is raised.
This class must be created inside of exception handler or `exception_info` must be specified
in creation process.
If you register handler for this exception then the Simpleline's exception handling
is disabled!
"""
def __init__(self, source, exception_info=None):
"""Create exception signal with higher priority (-20) than other signals.
:param source: source of this signal
:type source: class which emits this signal
:param exception_info: if specified raise your exception, otherwise create exception here;
to create exception here it needs to be created inside of exception
handler
:type exception_info: output of `sys.exc_info()` method
"""
super().__init__(source, priority=-20)
if exception_info:
self.exception_info = exception_info
else:
self.exception_info = exc_info()
class InputReadySignal(AbstractSignal):
"""Input from user is ready for processing."""
def __init__(self, source, input_handler_source, data, priority=0, success=True):
"""Store user input inside of this signal
Read the data from user input in `data` attribute.
The only way how a user should ask for input is to use InputHandler and inherited classes.
The input_handler_source param must be set but this signal instance can be attached to
another source object which is registered to a specific event loop.
If no requester (object who uses InputHandler) is specified then source and
input_handler_source will both point to InputHandler instance.
:param source: Source of this signal.
:type source: Any object.
:param input_handler_source: InputHandler who is asking for input.
:type input_handler_source: The `simpleline.input.input_handler.InputHandler` based
instance.
:param data: User input data.
:type data: str
:param priority: Priority of this event.
:type priority: Int greater than 0.
:param success: Was the input successful? True on successful input False otherwise.
:type success: bool
"""
super().__init__(source, priority=priority)
self.input_handler_source = input_handler_source
self.data = data
self.success = success
class InputReceivedSignal(AbstractSignal):
"""Raw input received.
This signal will be further processed and InputReadySignal should be enqueued soon.
Most probably you are looking for InputReadySignal instead.
"""
def __init__(self, source, data, priority=0):
super().__init__(source, priority=priority)
self.data = data
class RenderScreenSignal(AbstractSignal):
"""Render UIScreen to terminal."""
class CloseScreenSignal(AbstractSignal):
"""Close current screen."""
@@ -0,0 +1,81 @@
# Ticket machine synchronization class.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
class TicketMachine():
"""Hold signals processed by the event loop if someone wait on them.
This is useful when recursive process events will skip required signal.
"""
def __init__(self):
self._lines = {}
self._counter = 0
def take_ticket(self, line_id):
"""Take ticket (id) and go line (processing events).
Use `check_ticket` if you are ready to go.
:param line_id: Line where you are waiting.
:type line_id: Anything.
"""
obj_id = self._counter
if line_id not in self._lines:
self._lines[line_id] = {obj_id: False}
else:
self._lines[line_id][obj_id] = False
self._counter += 1
return obj_id
def check_ticket(self, line, unique_id):
"""Check if you are ready to go.
If True the unique_id is not valid anymore.
:param unique_id: Your id used to identify you in the line.
:type unique_id: int
:param line: Line where you are waiting.
:type line: Anything.
:return: True if the ticket was already marked, False otherwise
:rtype: bool
"""
if self._lines[line][unique_id]:
return self._lines[line].pop(unique_id)
return False
def mark_line_to_go(self, line):
"""All in the `line` are ready to go.
Mark all tickets in the line as True.
:param line: Line which should processed.
:type line: Anything.
"""
if line in self._lines:
our_line = self._lines[line]
for key in our_line:
our_line[key] = True
@@ -0,0 +1,112 @@
# Global configuration for the whole application.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
from getpass import getpass
__all__ = ["GlobalConfiguration"]
DEFAULT_WIDTH = 80
DEFAULT_PASSWORD_FUNC = getpass
class GlobalConfiguration():
"""Class for global configuration of application defaults.
All stored data are persistent between App.initialize() calls and can be used before this call.
"""
def __init__(self):
self._width = DEFAULT_WIDTH
self._getpass = DEFAULT_PASSWORD_FUNC
self._run_with_empty_stack = False
@property
def width(self):
"""Get width of the application.
:returns: int
"""
return self._width
@width.setter
def width(self, width):
"""Set width of the application.
:param width: Number of characters which can be printed to one line.
:type width: int
"""
self._width = width
def clear_width(self):
"""Clear user defined width and set the default.
Default: 80 characters
"""
self._width = DEFAULT_WIDTH
@property
def password_function(self):
"""Get function to get user passwords from a console.
:returns: Function with one argument which is text representation of prompt.
"""
return self._getpass
@password_function.setter
def password_function(self, password_func):
"""Set function to get user passwords from a console.
:param password_func: Function to get password from a command line.
:type password_func: Function with one argument which is text representation of prompt.
"""
self._getpass = password_func
def clear_password_function(self):
"""Clear user defined password function and set the default.
Default: getpass.getpass function
"""
self._getpass = getpass
@property
def should_run_with_empty_stack(self):
"""Should test on empty screen stack when starting event loop.
:returns: If False the App.run() call will end with an exception (default), True otherwise.
"""
return self._run_with_empty_stack
@should_run_with_empty_stack.setter
def should_run_with_empty_stack(self, value):
"""Set if the App.run() call should end with an exception when screen stack is empty.
This can be valuable when you want to schedule a screen later by an other thread.
:param value: If False the App.run() call will end with an exception, if True it will
run with nothing displayed.
"""
self._run_with_empty_stack = value
def clear_should_run_with_empty_stack(self):
"""Clear user defined test to run with an empty screen stack.
Default: False
"""
self._run_with_empty_stack = False
@@ -0,0 +1,22 @@
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
from simpleline.input.input_handler import InputHandler, PasswordInputHandler
__all__ = ["InputHandler", "PasswordInputHandler"]
@@ -0,0 +1,292 @@
# Handle user input
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
import sys
from simpleline import App
from simpleline.event_loop.signals import InputReadySignal
from simpleline.render.widgets import TextWidget
from simpleline.input.input_threading import InputThreadManager, InputRequest
__all__ = ["InputHandler", "PasswordInputHandler"]
class InputHandler():
def __init__(self, callback=None, source=None):
"""Class to handle input from the terminal.
This class is designed to be instantiated on place where it should be used.
The main method is `get_input()` which is non-blocking asynchronous call. It can be used
as synchronous call be calling the `wait_on_input` method.
To get result from this class use the `value` property.
:param callback: You can specify callback which will be called when user give input.
:type callback: Callback function with one argument which will be user input.
:param source: Source of this input. It will be helpful in case of debugging an issue.
:type source: Class which will process an input from this InputHandler.
"""
super().__init__()
self._input = None
self._input_callback = callback
self._input_received = False
self._input_successful = False
self._skip_concurrency_check = False
self._source = source
App.get_event_loop().register_signal_handler(InputReadySignal,
self._input_received_handler)
def _input_received_handler(self, signal, args):
if signal.input_handler_source != self:
return
self._input_received = True
self._input_successful = signal.success
if not self._input_successful:
return
self._input = signal.data
# call async callback
if self._input_callback is not None:
cb = self._input_callback
self._input_callback = None
cb(self._input)
@property
def value(self):
"""Return user input.
:returns: String or None if no is input received.
"""
return self._input
@property
def source(self):
"""Get source of this input.
:returns: Anything probably UIScreen.
"""
return self._source
@property
def skip_concurrency_check(self):
"""Is this InputHandler skipping concurrency check?
:returns bool
"""
return self._skip_concurrency_check
@skip_concurrency_check.setter
def skip_concurrency_check(self, value):
"""Set if this InputHandler should skip concurrency check.
Note if you skip this check, you can have unexpected behavior. Use with caution.
:param value: True to skip the check, False if not.
"""
self._skip_concurrency_check = value
def set_callback(self, callback):
"""Set a callback to get user input asynchronously.
:param callback: Callback called when user write their input.
:type callback: Method with 1 argument which is user input: def cb(user_input)
"""
self._input_callback = callback
def input_received(self):
"""Was user input already received?
:returns: True if yes, False otherwise.
"""
return self._input_received
def wait_on_input(self):
"""Blocks execution till the user input is received.
Events will works as expected during this blocking.
Please check the `input_successful` method to test the input.
"""
# we already received input from user
if self._input_received:
return
while not self._input_received:
App.get_event_loop().process_signals(InputReadySignal)
def input_successful(self):
"""Was input successful?
:returns: bool
"""
return self._input_successful
def get_input(self, prompt):
"""Use prompt to ask for user input and wait (non-blocking) on user input.
This is an asynchronous call. If you want to wait for user input then use
the `wait_on_input` method. If you want to get results asynchronously then register
callback in constructor or by the `set_callback` method.
Check if user input was already received can be done by the `input_received` method call.
:param prompt: Ask user what you want to get.
:type prompt: String or Prompt instance.
:returns: User input.
:rtype: str
"""
self._clear_input()
self._invoke_input_thread(prompt)
def _invoke_input_thread(self, prompt):
thread_object = self.create_thread_object(prompt)
InputThreadManager.get_instance().start_input_thread(thread_object,
not self._skip_concurrency_check)
def create_thread_object(self, prompt):
"""Create thread object containing all the information how to get user input.
:returns: Instance of class inherited from `simpleline.input.InputThread`.
"""
return InputHandlerRequest(App.get_configuration().width, prompt, self)
def _clear_input(self):
self._input_received = False
self._input = None
class InputHandlerRequest(InputRequest):
"""This is thread object to get input from user without blocking main thread."""
def __init__(self, width, prompt, input_handler):
"""Create request object to get input in InputThreadManager.
:param width: Width of the screen prompt.
:type width: int
:param prompt: Input prompt.
:type prompt: Instance of `simpleline.render.prompt.Prompt` class.
:param input_handler: InputHandler instance which created this object.
:type input_handler: InputHandler based instance.
"""
super().__init__(input_handler, input_handler.source)
self._width = width
self._prompt = prompt
def get_input(self):
"""This method is responsible for interruptable user input.
It is expected to be used in a thread started on demand
and returns the input via the communication Queue.
"""
# lock acquired, we can run input
try:
data = self._ask_input()
except EOFError:
data = ""
return data
def text_prompt(self):
widget = TextWidget(str(self._prompt))
widget.render(self._width)
lines = widget.get_lines()
return "\n".join(lines) + " "
def _ask_input(self):
text_prompt = self.text_prompt()
sys.stdout.write(text_prompt)
sys.stdout.flush()
return self._get_input()
@staticmethod
def _get_input():
return input()
class PasswordInputHandler(InputHandler):
def __init__(self, callback=None, source=None):
"""Class to handle hidden password input from the terminal.
This class is designed to be instantiated on place where it should be used.
The main method is `get_input()` which is non-blocking asynchronous call. It can be used
as synchronous call be calling the `wait_on_input` method.
To get result from this class use the `value` property.
:param callback: You can specify callback which will be called when user give input.
:type callback: Callback function with one argument which will be user input.
:param source: Source of this input. It will be helpful in case of debugging an issue.
:type source: Class which will process an input from this InputHandler.
"""
super().__init__(callback=callback, source=source)
self._getpass_func = App.get_configuration().password_function
def set_pass_func(self, getpass_func):
"""Set a function for getting passwords."""
if not getpass_func:
return
self._getpass_func = getpass_func
def create_thread_object(self, prompt):
"""Return PasswordInputThread for getting user password."""
return PasswordInputHandlerRequest(App.get_configuration().width, prompt, self,
self._getpass_func)
class PasswordInputHandlerRequest(InputHandlerRequest):
"""Similar as InputHandlerRequest but don't echo user keys."""
def __init__(self, width, prompt, input_handler, getpass_func):
"""Create request object to get password input in InputThreadManager.
:param width: Width of the screen prompt.
:type width: int
:param prompt: Input prompt.
:type prompt: Instance of `simpleline.render.prompt.Prompt` class.
:param input_handler: InputHandler instance which created this object.
:type input_handler: InputHandler based instance.
:param getpass_func: Function to get user password.
:type getpass_func: Function which gets prompt as only parameter and returns user input
string.
"""
super().__init__(width, prompt, input_handler)
self._getpass_func = getpass_func
def _ask_input(self):
text_prompt = self.text_prompt()
return self._getpass_func(text_prompt)
@@ -0,0 +1,238 @@
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
import threading
from abc import ABCMeta, abstractmethod
from simpleline import App
from simpleline.logging import get_simpleline_logger
from simpleline.event_loop.signals import InputReceivedSignal, InputReadySignal
log = get_simpleline_logger()
INPUT_THREAD_NAME = "SimplelineInputThread"
class InputThreadManager():
"""Manager object for input threads.
This manager helps with concurrent user input (still you really shouldn't do that).
"""
__instance = None
def __init__(self):
super().__init__()
self._input_stack = []
self._processing_input = False
@classmethod
def create_new_instance(cls):
instance = InputThreadManager()
cls.__instance = instance
instance._post_init_configuration() # pylint: disable=protected-access
def _post_init_configuration(self):
# pylint: disable=protected-access
App.get_event_loop().register_signal_handler(InputReceivedSignal,
self.__instance._input_received_handler)
@classmethod
def get_instance(cls):
if not cls.__instance:
cls.create_new_instance()
return cls.__instance
def _input_received_handler(self, signal, args):
thread_object = self._input_stack.pop()
thread_object.emit_input_ready_signal(signal.data)
if thread_object.thread:
thread_object.thread.join()
# wait until used object ends
for t in self._input_stack:
t.emit_failed_input_ready_signal()
if t.thread:
t.thread.join()
# remove all other items waiting for input
self._input_stack.clear()
self._processing_input = False
def start_input_thread(self, input_thread_object, concurrent_check=True):
"""Start input thread to get user input.
:param input_thread_object: Input thread object based on InputThread class.
:param concurrent_check: Should the concurrent thread check be fatal? (default True).
"""
self._input_stack.append(input_thread_object)
self._check_input_thread_running(concurrent_check)
self._start_user_input_async()
def _check_input_thread_running(self, raise_concurrent_check):
if len(self._input_stack) != 1:
if not raise_concurrent_check:
log.warning("Asking for multiple inputs with concurrent check bypassed, "
"last who asked wins! Others are dropped.")
else:
msg = ""
for t in self._input_stack:
requester_source = t.requester_source or "Unknown"
msg += "Input handler: {} Input requester: {}\n".format(t.source,
requester_source)
msg.rstrip()
raise KeyError("Can't run multiple input threads at the same time!\n"
"Asking for input:\n"
"{}".format(msg))
def _start_user_input_async(self):
thread_object = self._input_stack[-1]
if self._processing_input:
self._print_new_prompt(thread_object)
return
thread_object.initialize_thread()
self._processing_input = True
thread_object.start_thread()
@staticmethod
def _print_new_prompt(thread_object):
prompt = thread_object.text_prompt()
# print new prompt
print(prompt, end="")
class InputRequest(metaclass=ABCMeta):
"""Base input request class.
This should be overloaded for every InputHandler class. Purpose of this class is to print
prompt and get input from user.
The `run_input` method is the entry point for this class. Output from this method must be
a user input.
The `text_prompt` method is used to get textual representation of a prompt. This will be used
on concurrent input to replace existing prompt to get new input.
WARNING:
The `run_input` method will run in a separate thread!
"""
def __init__(self, source, requester_source=None):
super().__init__()
self._source = source
self._requester_source = requester_source
self.thread = None
@property
def source(self):
"""Get direct source of this input request.
:returns: InputHandler instance.
"""
return self._source
@property
def requester_source(self):
"""Get requester -- source of this input.
:returns: Anything probably UIScreen based instance.
"""
return self._requester_source
def emit_input_ready_signal(self, input_data):
"""Emit the InputReadySignal signal with collected input data.
:param input_data: Input data received.
:type input_data: str
"""
handler_source = self.source
signal_source = self._get_request_source()
new_signal = InputReadySignal(source=signal_source, input_handler_source=handler_source,
data=input_data, success=True)
App.get_event_loop().enqueue_signal(new_signal)
def emit_failed_input_ready_signal(self):
"""Emit the InputReadySignal with failed state."""
handler_source = self.source
signal_source = self._get_request_source()
new_signal = InputReadySignal(source=signal_source, input_handler_source=handler_source,
data="", success=False)
App.get_event_loop().enqueue_signal(new_signal)
def _get_request_source(self):
"""Get user input request source.
That means object who is using InputHandler.
If this object is not specified then return InputHandler as a source.
"""
return self.requester_source or self.source
def initialize_thread(self):
"""Initialize thread for this input request.
Do not call this directly! Will be called by InputThreadManager.
"""
self.thread = threading.Thread(name=INPUT_THREAD_NAME, target=self.run)
self.thread.daemon = True
def start_thread(self):
"""Start input thread.
Do not call this directly! Will be called by InputThreadManager.
"""
self.thread.start()
def run(self):
"""Run the `run_input` method and propagate input outside.
Do not call this method directly. It will be called by InputThreadManager.
"""
data = self.get_input()
App.get_event_loop().enqueue_signal(InputReceivedSignal(self, data))
@abstractmethod
def text_prompt(self):
"""Get text representation of the user prompt.
This will be used to get high priority input.
:returns: String representation of the prompt or None if no prompt is present.
"""
return None
@abstractmethod
def get_input(self):
"""Print prompt and get an input from user.
..NOTE: Overload this method in your class.
Return this input from a function.
"""
return ""
@@ -0,0 +1,39 @@
# Logging functions and methods used by Simpleline.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
import logging
SIMPLELINE_LOGGER = "simpleline"
def setup_logging():
"""Set proper logging for a library"""
log = get_simpleline_logger()
null_hd = logging.NullHandler()
log.addHandler(null_hd)
def get_simpleline_logger():
"""Return logging instance that can be used in the application."""
return logging.getLogger(SIMPLELINE_LOGGER)
@@ -0,0 +1,31 @@
# Module with classes to handle rendering and input.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from simpleline.errors import SimplelineError
class RenderError(SimplelineError):
"""Exception raised when error in rendering happens."""
class RenderUnexpectedError(RenderError):
"""Exception raised when something goes really wrong."""
@@ -0,0 +1,253 @@
# Advanced widgets
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
import sys
from simpleline.render import widgets
from simpleline.render.containers import WindowContainer
from simpleline.render.prompt import Prompt
from simpleline.render.screen import UIScreen, InputState
from simpleline.input.input_handler import PasswordInputHandler
from simpleline.utils.i18n import _, N_, C_
__all__ = ["ErrorDialog", "GetInputScreen", "GetPasswordInputScreen", "HelpScreen",
"PasswordDialog", "YesNoDialog"]
class ErrorDialog(UIScreen):
"""Dialog screen for reporting errors to user."""
def __init__(self, message):
"""
:param message: the message to show to the user
:type message: str
"""
super().__init__()
self.title = N_("Error")
self._message = message
def refresh(self, args=None):
super().refresh(args)
text = widgets.TextWidget(self._message)
self.window.add_with_separator(widgets.CenterWidget(text))
def prompt(self, args=None):
return Prompt(_("Press %s to exit") % Prompt.ENTER)
def input(self, args, key):
"""This dialog is closed by any input.
And causes the program to quit.
"""
sys.exit(1)
class PasswordDialog(UIScreen):
"""Dialog screen for password input."""
def __init__(self, message=None):
"""
:param message: password prompt question
:type message: string
"""
super().__init__()
self.title = N_("Password")
self._message = message or _("Enter your passphrase")
self._password = None
def refresh(self, args=None):
super().refresh(args)
text = widgets.TextWidget(self._message)
self.window.add_with_separator(widgets.CenterWidget(text))
def prompt(self, args=None):
handler = PasswordInputHandler(source=self)
if self.password_func:
handler.set_pass_func(self.password_func)
handler.get_input(_("Passphrase: "))
handler.wait_on_input()
if not handler.input_successful():
return None
self._password = handler.value
# this may seem innocuous, but it's really a giant hack; we should
# not be calling close() from prompt(), but the input handling code
# in the TUI is such that without this very simple workaround, we
# would be forever pelting users with a prompt to enter their pw
self.close()
return None
@property
def answer(self):
"""The response can be None (no response) or the password entered."""
return self._password
def input(self, args, key):
if key:
self._password = key
return InputState.PROCESSED_AND_CLOSE
return InputState.DISCARDED
class YesNoDialog(UIScreen):
"""Dialog screen for Yes - No questions."""
def __init__(self, message):
"""
:param message: the message to show to the user
:type message: unicode
"""
super().__init__()
self.title = N_("Question")
self._message = message
self._response = None
def refresh(self, args=None):
super().refresh(args)
text = widgets.TextWidget(self._message)
self.window.add_with_separator(widgets.CenterWidget(text))
def prompt(self, args=None):
return Prompt(_("Please respond '%(yes)s' or '%(no)s'") % {
# TRANSLATORS: 'yes' as positive reply
"yes": C_('TUI|Spoke Navigation', 'yes'),
# TRANSLATORS: 'no' as negative reply
"no": C_('TUI|Spoke Navigation', 'no')
})
def input(self, args, key):
# TRANSLATORS: 'yes' as positive reply
if key == C_('TUI|Spoke Navigation', 'yes'):
self._response = True
return InputState.PROCESSED_AND_CLOSE
# TRANSLATORS: 'no' as negative reply
if key == C_('TUI|Spoke Navigation', 'no'):
self._response = False
return InputState.PROCESSED_AND_CLOSE
return InputState.DISCARDED
@property
def answer(self):
"""The response can be True (yes), False (no) or None (no response)."""
return self._response
class HelpScreen(UIScreen):
"""Screen to display a help message."""
def __init__(self, help_path):
"""
:param help_path: help file name
:type help_path: str
"""
super().__init__()
self.title = N_("Help")
self.help_path = help_path
def refresh(self, args=None):
""" Show the help. """
super().refresh(args)
help_message = _("The help is not available.")
if self.help_path:
with open(self.help_path, 'r') as f:
help_message = f.read()
self.window.add_with_separator(widgets.TextWidget(help_message))
def input(self, args, key):
""" Handle user input. """
return InputState.PROCESSED_AND_CLOSE
def prompt(self, args=None):
return Prompt(_("Press %s to return") % Prompt.ENTER)
class GetInputScreen(UIScreen):
"""Screen for getting user input."""
def __init__(self, message):
"""
:param message: Prompt printed before user input.
:type message: str
"""
super().__init__()
self._message = message
self._value = None
self._conditions = []
@property
def value(self):
"""User input."""
return self._value
def add_acceptance_condition(self, acceptance_function, args=None):
"""Add acceptance condition to the conditions list.
:param acceptance_function: Functions that accepts or rejects a user input.
:type acceptance_function: `function(input, args) -> bool` - function which takes
user input (string) and arguments (`args`) and return True when
input is accepted or False if rejected so we will ask for
a new input.
:param args: Second argument for `acceptance_function` the first one will be user input.
:type args: Anything.
"""
self._conditions.append((acceptance_function, args))
def clear_acceptance_conditions(self):
"""Clear list of the acceptance conditions."""
self._conditions.clear()
def refresh(self, args=None):
super().refresh(args)
self._window = WindowContainer()
def prompt(self, args=None):
return Prompt(message=self._message)
def input(self, args, key):
if not self._test_input(key):
return InputState.DISCARDED
self._value = key
return InputState.PROCESSED_AND_CLOSE
def _test_input(self, key):
for f, args in self._conditions:
if not f(key, args):
return False
return True
class GetPasswordInputScreen(GetInputScreen):
"""Screen for getting user password input."""
def __init__(self, message):
super().__init__(message)
self.hide_user_input = True
@@ -0,0 +1,475 @@
# Widgets for holding other widgets.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
from math import ceil
from simpleline.render.widgets import Widget, TextWidget, SeparatorWidget
from simpleline.logging import get_simpleline_logger
__all__ = ["ListRowContainer", "ListColumnContainer", "WindowContainer"]
log = get_simpleline_logger()
class Container(Widget):
"""Base class for containers which will do positioning of the widgets."""
def __init__(self, items=None, numbering=True):
"""Construct Container.
:param items: List of items for positioning in this Container. Callback
can't be specified this way.
:type items: List of items for rendering.
:param numbering: Enable/disable automatic numbering (labels) for items.
Enabled by default (True).
:type numbering: bool
"""
super().__init__()
self._key_pattern = None
self._items = []
if items:
for i in items:
self._items.append(ContainerItem(i))
if numbering:
self._key_pattern = KeyPattern()
else:
self._key_pattern = None
@property
def size(self):
"""Return items count."""
return len(self._items)
@property
def key_pattern(self):
"""Return key pattern which will be used for items numbering.
Will return `None` if not set.
"""
return self._key_pattern
@key_pattern.setter
def key_pattern(self, key_pattern):
"""Set the key pattern object which will be used for items numbering.
Setting `None` will stop doing numbering.
"""
self._key_pattern = key_pattern
def add(self, item, callback=None, data=None):
"""Add item to the Container.
:param item: Add item to this container.
:type item: Could be item (based on `simpleline.render.widgets.Widget`)
or other container (based on `simpleline.render.containers.Container`).
:param callback: Add callback for this item. This callback will be called when user
activate this `item`.
:type callback: function ``func(data)``.
:param data: Data which will be passed to the callback.
:param data: Anything.
:returns: ID of the item in this Container.
:rtype: int
"""
self._items.append(ContainerItem(item, callback, data))
return len(self._items) - 1
def process_user_input(self, key):
"""Process input from the user if any of the items in the list was called.
This method must be called in `UIScreen.input()` method if list widget should call
the callbacks.
:param key: Key pressed from user.
:type key: str
:returns: True if key was processed. False otherwise.
"""
if not self._key_pattern:
return False
if not isinstance(key, str):
return False
res = self._key_pattern.translate_input_to_widget_id(key)
if res is not None and res >= 0:
try:
item = self._items[res]
if item.callback is not None:
item.callback(item.data)
return True
except IndexError: # container widget with this id doesn't exists
return False
return False
def create_number_label(self, item_id):
"""Create TextWidget from KeyPattern.
:param item_id: Create label for item with this id.
:type item_id: int
:returns: Widget with label for the item with item_id.
:rtype: `simpleline.render.widgets.TextWidget` instance.
"""
number_widget = TextWidget(self._key_pattern.get_widget_label(item_id))
return number_widget
class WindowContainer(Container):
"""Base container for screens.
This can hold other containers or Widgets for rendering.
"""
def __init__(self, title=None):
"""Construct base container for screens.
This container doesn't have numbering support. Input other containers in it to
allow numbering and input processing.
:param title: Title line with separator after this title.
:type title: str
"""
super().__init__(numbering=False)
self._title = title
def add_with_separator(self, item, callback=None, data=None, blank_lines=1):
"""Add widget and after widget add blank line.
This method will call
`self.add(item, callback, data)`
`self.add_separator(lines)`
:param item: Add item to this container.
:type item: Could be item (based on `simpleline.render.widgets.Widget`)
or other container (based on `simpleline.render.containers.Container`).
:param callback: Add callback for this item. This callback will be called when user
activate this `item`.
:type callback: function ``func(data)``.
:param data: Data which will be passed to the callback.
:param data: Anything.
:param blank_lines: How many blank lines should be printed.
:type blank_lines: int greater than 0.
:returns: ID of the item in this Container.
:rtype: int
"""
item_id = self.add(item, callback, data)
self.add_separator(blank_lines)
return item_id
def add_separator(self, lines=1):
"""Add blank lines between widgets.
:param lines: How many blank lines should be printed.
:type lines: int greater than 0.
"""
self.add(SeparatorWidget(lines))
@property
def title(self):
"""Title of WindowContainer."""
return self._title
def render(self, width):
"""Render widgets to it's internal buffer.
:param width: the maximum width the item can use
:type width: int
:return: nothing
"""
super().render(width)
# set cursor position to top-left corner
self.set_cursor_position(0, 0)
if self._title:
self._draw_title_and_separator(width)
for item in self._items:
widget = item.widget
widget.render(width)
self.draw(widget)
def _draw_title_and_separator(self, width):
title_widget = TextWidget(self._title)
sep = SeparatorWidget()
title_widget.render(width)
sep.render(width)
self.draw(title_widget)
self.draw(sep)
class ListRowContainer(Container):
"""Place widgets in rows automatically.
Compared to the ColumnWidget this is able to handle word wrapping correctly.
There is numbering N) automatically for all items. To disable this feature call
`self.key_pattern = None`. If you want other numbering then look on `KeyPattern` class.
Widgets will be placed based on the number of columns in the following way:
1) w1 2) w2 3) w3
4) w4 5) w5 6) w6
....
"""
def __init__(self, columns, items=None, columns_width=None, spacing=3, numbering=True):
"""Create ListWidget with specific number of columns.
:param columns: How many columns we want.
:type columns: int, bigger than 0
:param items: List of items for positioning in this Container. Callback can't be
specified this way.
:type items: List of items for rendering.
:param columns_width: Width of every column. If nothing specified the maximum width
will be distributed to columns.
:type columns_width: int or None
:param spacing: Set the spacing between columns.
:type spacing: int
:param numbering: Enable/disable automatic numbering (labels) for items.
Enabled by default (True).
:type numbering: bool
"""
super().__init__(items, numbering)
self._columns = columns
self._columns_width = columns_width
self._spacing = spacing
self._numbering_widgets = []
def render(self, width):
"""Render widgets to it's internal buffer.
:param width: the maximum width the item can use
:type width: int
:return: nothing
"""
super().render(width)
if self._columns_width is None:
spaces_between_columns = self._columns - 1
sum_spacing = spaces_between_columns * self._spacing
self._columns_width = int((width - sum_spacing) / self._columns)
ordered_map = self._get_ordered_map()
lines_per_rows = self._lines_per_every_row(ordered_map)
# the leftmost empty column
col_pos = 0
for col in ordered_map:
row_pos = 0
# render and draw contents of column
for row_id, item_id in enumerate(col):
container = self._items[item_id]
widget = container.widget
# set cursor to first line and leftmost empty column
self.set_cursor_position(row_pos, col_pos)
if self._key_pattern is not None:
number_widget = self._numbering_widgets[item_id]
widget_width = len(number_widget.text)
self.draw(number_widget)
self.set_cursor_position(row_pos, col_pos + widget_width)
self.draw(widget, block=True)
row_pos = row_pos + lines_per_rows[row_id]
# recompute the leftmost empty column
col_pos = max((col_pos + self._columns_width), self.width) + self._spacing
def _lines_per_every_row(self, items):
self._render_all_items()
# call `self._render_and_calculate_lines_per_rows()` method instead
lines_per_row = []
# go through all items and find how many lines we need for each row
# printed (because of wrapping)
for column_items in items:
for row_id, item_id in enumerate(column_items):
item = self._items[item_id]
if len(lines_per_row) <= row_id:
lines_per_row.append(0)
lines_per_row[row_id] = max(lines_per_row[row_id], len(item.widget.get_lines()))
return lines_per_row
def _render_all_items(self):
for item_id, item in enumerate(self._items):
item_width = self._columns_width
if item_width <= 0:
raise ValueError("Widget can't be rendered! Columns width is too small.")
if self._key_pattern:
number_widget = self.create_number_label(item_id)
# render numbers before widgets
number_width = len(number_widget.text)
number_widget.render(number_width)
self._numbering_widgets.append(number_widget)
# reduce the size of widget because of the number
item_width -= number_width
if item_width <= 0:
raise ValueError("Widget can't be rendered with numbering on! "
"Increase column width or disable numbering.")
item.widget.render(item_width)
def _get_ordered_map(self):
"""Return list of identifiers (index) to the original item list.
.. NOTE: Use of ``self._prepare_list()` is encouraged to create output list and
just fill up this list.
"""
# create list of columns (lists)
ordering_map = self._prepare_list()
for item_id in range(self.size):
ordering_map[item_id % self._columns].append(item_id)
return ordering_map
def _prepare_list(self):
"""Prepare list for items ordering to rows and columns.
List will be prepared as ([column 1], [column 2], ...)
"""
return list(map(lambda x: [], range(0, self._columns)))
class ListColumnContainer(ListRowContainer):
"""Place widgets in columns automatically.
Compared to the ColumnWidget this is able to handle word wrapping correctly.
There is numbering N) automatically for all items. To disable this feature call
`self.key_pattern = None`. If you want other numbering then look on `KeyPattern` class.
Widgets will be placed based on the number of columns in the following way:
1) w1 4) w4 7) w7
2) w2 5) w5 8) w8
3) w3 6) w6 9) w9
"""
def _get_ordered_map(self):
ordering_map = self._prepare_list()
items_in_column = ceil(len(self._items) / self._columns)
for item_id in range(self.size):
col_position = int(item_id // items_in_column)
ordering_map[col_position].append(item_id)
return ordering_map
class KeyPattern():
"""Pattern for automatic key printing before items."""
def __init__(self, pattern="{:d}) ", offset=1):
"""Create the pattern class.
For enabling greater functionality than python 3 format is able to do, feel free to
override this class and use your subclass instead.
:param pattern: Set pattern which will be called for every item.
:type pattern: Strings format method.
See https://docs.python.org/3.3/library/string.html#format-string-syntax.
:param offset: Set the offset for numbering items. Default is 1 to start indexing
naturally for user.
:type offset: int
"""
self._pattern = pattern
self._offset = offset
def get_widget_label(self, item_id):
"""Get widget identifier for user input description.
It should be something similar to the pattern.
:param item_id: Position of the widget in the list.
:type item_id: int starts from 0.
"""
return self._pattern.format(item_id + self._offset)
def translate_input_to_widget_id(self, user_input):
"""Get id of the widget from the user input.
This is reverse translation to `self.get_widget_identifier()`.
:param user_input: Input from user:
:type user_input: str
:return: ID of the widget in the list or None if the input can't be translated.
:rtype: int or None
"""
try:
return int(user_input) - 1
except ValueError:
log.debug("No callback registered for user input %s", user_input)
return None
class ContainerItem():
"""Item used inside of containers to store widgets callbacks and data.
Internal representation for Containers. Do not use this class directly.
"""
def __init__(self, widget, callback=None, data=None):
"""Construct WidgetContainer.
:param widget: Any item from `simpleline.render.widgets` or `Container`.
:type widget: Class subclassing the `simpleline.render.widgets.Widget` class
or `simpleline.render.containers.Container`.
:param callback: This callback will be called as reaction on user input.
:type callback: Function with one data parameter: `def func(data):`.
:param data: Params which will be passed to callback.
:type data: Anything.
"""
self.widget = widget
self.callback = callback
self.data = data
@@ -0,0 +1,154 @@
# Class for the Anaconda TUI prompt.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Vendula Poncova <vponcova@redhat.com>
#
import logging
from simpleline.utils.i18n import N_, _
log = logging.getLogger("simpleline")
class Prompt():
"""Class to create a prompt message with options."""
# Default message of the prompt
DEFAULT_MESSAGE = N_("Please make a selection from the above")
# String to use in a prompt when we want users to press the key ENTER.
ENTER = N_("ENTER")
# TRANSLATORS: 'q' to quit
QUIT_DESCRIPTION = N_("to quit")
QUIT = 'q'
# TRANSLATORS:'c' to continue
CONTINUE_DESCRIPTION = N_("to continue")
CONTINUE = 'c'
# TRANSLATORS:'r' to refresh
REFRESH_DESCRIPTION = N_("to refresh")
REFRESH = 'r'
# TRANSLATORS:'h' to help
HELP_DESCRIPTION = N_("to help")
HELP = 'h'
def __init__(self, message=DEFAULT_MESSAGE):
"""
:param message: the message of the prompt
:type message: str|None
"""
self.message = message
self.options = dict()
def set_message(self, message):
"""Set the prompt message.
:param message: the message of the prompt
:type message: str|None
"""
self.message = message
def add_option(self, key, description):
"""Add an option to the prompt.
Causes a warning if the option already exists.
:param key: the key for choosing the option
:type key: str
:param description: the description of the option
:type description: str
"""
if key in self.options:
log.warning("The option '%s' does already exist in '%s'.", key, self)
self.options[key] = description
def update_option(self, key, description):
"""Update an option in the prompt.
Causes a warning if the option does not exist.
:param key: the key for choosing the option
:type key: str
:param description: the description of the option
:type description: str
"""
if key not in self.options:
log.warning("The option '%s' does not exist in '%s'.", key, self)
self.options[key] = description
def add_refresh_option(self, description=REFRESH_DESCRIPTION):
"""Add the option to refresh."""
if Prompt.REFRESH in self.options:
self.update_option(Prompt.REFRESH, description)
else:
self.add_option(Prompt.REFRESH, description)
def add_continue_option(self, description=CONTINUE_DESCRIPTION):
"""Add the option to continue."""
if Prompt.CONTINUE in self.options:
self.update_option(Prompt.CONTINUE, description)
else:
self.add_option(Prompt.CONTINUE, description)
def add_quit_option(self, description=QUIT_DESCRIPTION):
"""Add the option to quit."""
if Prompt.QUIT in self.options:
self.update_option(Prompt.QUIT, description)
else:
self.add_option(Prompt.QUIT, description)
def add_help_option(self, description=HELP_DESCRIPTION):
"""Add the option to help."""
if Prompt.HELP in self.options:
self.update_option(Prompt.HELP, description)
else:
self.add_option(Prompt.HELP, description)
def remove_option(self, key):
"""Remove an option with the given key.
:param key: the key of the option
:type key: str
:return: the removed option
:rtype: str|None
"""
return self.options.pop(key, None)
def __str__(self):
"""Return the string representation of the prompt."""
if not self.message and not self.options:
return ""
parts = []
if self.message:
parts.append(_(self.message))
if self.options:
opt_list = ["'%s' %s" % (key, _(self.options[key]))
for key in sorted(self.options.keys())]
opt_str = "[%s]" % ", ".join(opt_list)
parts.append(opt_str)
return " ".join(parts) + ": "
@@ -0,0 +1,313 @@
# Base class for text window screens.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from enum import Enum
from simpleline import App
from simpleline.render.containers import WindowContainer
from simpleline.render.prompt import Prompt
from simpleline.render.screen.signal_handler import SignalHandler
from simpleline.render.screen.input_manager import InputManager
from simpleline.utils.i18n import _
__all__ = ["UIScreen", "InputState"]
class UIScreen(SignalHandler):
"""Base class representing one TUI Screen.
Shares some API with anaconda's GUI to make it easy for devs to create similar UI
with the familiar API.
"""
def __init__(self, title=None, screen_height=30):
""" Constructor of the TUI screen.
:param title: Title line of the screen.
:type title: str
:param screen_height: height of the screen (useful for printing long widgets)
:type screen_height: int (the value must be bigger than 4)
"""
self._title = title
self._screen_height = screen_height
self._screen_ready = False
# ask for password
self._hide_user_input = False
self._password_func = None
# do not print separator for this screen
self._no_separator = False
# list that holds the content to be printed out
self._window = WindowContainer(self.title)
# should the input be required after draw
self._input_required = True
# index of the page (subset of screen) shown during show_all
# indexing starts with 0
self._page = 0
self._input_manager = InputManager(ui_screen=self)
def __str__(self):
"""For easier logging."""
return self.__class__.__name__
@property
def title(self):
"""Screen title."""
return self._title
@title.setter
def title(self, title):
"""Set screen title.
Set `None` to remove title.
"""
self._title = title
@property
def password_func(self):
"""Get password function.
This is function with one argument to get password from command line.
"""
return self._password_func
@password_func.setter
def password_func(self, value):
"""Set password function.
:param value: Function to get password from a command line.
:type value: Function with one argument which is text representation of prompt.
"""
self._password_func = value
@property
def screen_ready(self):
"""This screen is ready for use."""
return self._screen_ready
@screen_ready.setter
def screen_ready(self, screen_ready):
"""Set ready status for this screen."""
self._screen_ready = screen_ready
@property
def input_required(self):
"""Return if the screen requires input."""
return self._input_required
@input_required.setter
def input_required(self, input_required):
"""Set if the screen should require input."""
self._input_required = input_required
@property
def no_separator(self):
"""Should we print separator for this screen?
:returns: True to print separator before this screen (default).
False do not print separator.
"""
return self._no_separator
@no_separator.setter
def no_separator(self, no_separator):
"""Print or do not print separator.
:param no_separator: Specify if the separator should be printed.
:type no_separator: bool (default: False).
"""
self._no_separator = no_separator
@property
def hide_user_input(self):
"""Hide typed user input.
This is main solution how to ask for password.
:returns: True if user input should be hidden.
False otherwise (default).
"""
return self._hide_user_input
@hide_user_input.setter
def hide_user_input(self, hide_input):
"""Should be the user input hidden.
:param hide_input: True if user input should be hidden.
False if not (default).
:type hide_input: bool (default: False).
"""
self._hide_user_input = hide_input
@property
def window(self):
"""Return WindowContainer instance."""
return self._window
@window.setter
def window(self, window):
"""Set base WindowContainer instance.
:param window: Base window container containing other widgets and containers.
:type window: Instance of `simpleline.render.containers.WindowContainer` class.
"""
self._window = window
def get_user_input(self, message, hidden=False):
"""Get immediately input from the user.
Use this with cautious. Never call this in middle of rendering or when other
input is already waiting. It is recommended to use `self.input_required` instead.
:param message: Message prompt for the user.
:type message: str
:param hidden: Do not echo user input (password typing).
:type hidden: bool
"""
return self._input_manager.get_input_blocking(message, hidden)
def setup(self, args):
"""Do additional setup right before this screen is used.
It is mandatory to call this ancestor method in the child class to set ready status.
:param args: arguments for the setup
:type args: array of values
:return: whether this screen should be scheduled or not
:rtype: bool
"""
self._screen_ready = True
App.get_event_loop().register_signal_source(self)
return True
def refresh(self, args=None):
"""Method which prepares the content desired on the screen to `self.window`.
:param args: optional argument passed from switch_screen calls
:type args: anything
"""
self.window = WindowContainer(self._title)
def _print_widget(self, widget):
"""Prints a widget with user interaction (when needed).
Could be longer than the screen height.
:param widget: widget to print
:type widget: Widget instance
"""
# TODO: Work even for lower screen_height than 4
pos = 0
lines = widget.get_lines()
num_lines = len(lines)
if num_lines == 0:
return
prompt_height = 2
real_screen_height = self._screen_height - prompt_height
if num_lines < real_screen_height:
# widget plus prompt are shorter than screen height, just print the widget
print(u"\n".join(lines))
return
# long widget, print it in steps and prompt user to continue
last_line = num_lines - 1
while pos <= last_line:
if pos + real_screen_height > last_line:
# enough space to print the rest of the widget plus regular
# prompt (2 lines)
for line in lines[pos:]:
print(line)
pos += self._screen_height - 1
else:
# print part with a prompt to continue
for line in lines[pos:(pos + real_screen_height)]:
print(line)
custom_prompt = Prompt(_("\nPress %s to continue") % Prompt.ENTER)
self._ask_user_input_blocking(custom_prompt)
pos += real_screen_height
def _ask_user_input_blocking(self, prompt):
return self._input_manager.get_input_blocking(prompt, False)
def show_all(self):
"""Print WindowContainer in `self.window` with all its content."""
self.window.render(App.get_configuration().width)
self._print_widget(self.window)
def input(self, args, key):
"""Method called to process input. If the input is not handled here, return it.
:param key: input string to process
:type key: str
:param args: optional argument passed from switch_screen calls
:type args: anything
:return: return `simpleline.render.InputState.PROCESSED` if key was handled,
`simpleline.render.InputState.DISCARDED` if the screen should not process input
on the scheduler and key if you want it to.
:rtype: `simpleline.render.InputState` enum | str
"""
return key
def get_input_with_error_check(self, args):
"""Get user input and redraw if user add too many invalid inputs.
This method should be used only by ScreenScheduler.
:param args: Arguments passed in when scheduling this screen.
:type args: Anything.
"""
self._input_manager.get_input(args=args)
def prompt(self, args=None):
"""Return the text to be shown as prompt or handle the prompt and return None.
:param args: optional argument passed from switch_screen calls
:type args: anything
:return: returns an instance of Prompt with text to be shown next to the prompt
for input or None to skip further input processing
:rtype: Prompt instance|None
"""
prompt = Prompt()
prompt.add_refresh_option()
prompt.add_continue_option()
prompt.add_quit_option()
return prompt
def closed(self):
"""Callback when this screen is closed."""
class InputState(Enum):
PROCESSED = 1
PROCESSED_AND_REDRAW = 2
PROCESSED_AND_CLOSE = 3
DISCARDED = 0
@@ -0,0 +1,203 @@
# Class for managing input and output for application.
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline. If not, see <https://www.gnu.org/licenses/>.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
from enum import Enum
from simpleline import App
from simpleline.event_loop import ExitMainLoop
from simpleline.event_loop.signals import ExceptionSignal
from simpleline.render.prompt import Prompt
from simpleline.input import InputHandler, PasswordInputHandler
from simpleline.logging import get_simpleline_logger
log = get_simpleline_logger()
class InputManager():
def __init__(self, ui_screen):
"""Processor for user input.
This class is mainly helper class for ScreenScheduler.
:param ui_screen: Screen associated with this input manager.
:type ui_screen: The `simpleline.render.screen.UIScreen` based instance.
"""
super().__init__()
self._ui_screen = ui_screen
self._input_error_counter = 0
self._input_error_threshold = 5
self._input_args = None
@property
def input_error_counter(self):
"""Return how many times the user provided bad input."""
return self._input_error_counter
@property
def input_error_threshold_exceeded(self):
"""Did the error counter pass the threshold?
The screen should be redraw.
"""
errors = self._input_error_counter % self._input_error_threshold
return errors == 0
def get_input_blocking(self, message, hidden):
"""Get blocking input from the user.
:param message: Message prompt for the user.
:type message: str
:param hidden: Do not echo user input (password typing).
:type hidden: bool
"""
if hidden:
handler = PasswordInputHandler(source=self)
if self._ui_screen.password_func:
handler.set_pass_func(self._ui_screen.password_func)
else:
handler = InputHandler(source=self)
handler.get_input(message)
handler.wait_on_input()
return handler.value
def get_input(self, args=None):
"""Get input from user.
:param args: Arguments passed in when UIScreen was scheduled.
:type args: Anything.
"""
prompt = self._ui_screen.prompt(args)
if not self._is_input_expected(prompt):
return
self._input_args = args
if not self._ui_screen.hide_user_input:
handler = InputHandler(source=self._ui_screen)
else:
handler = PasswordInputHandler(source=self._ui_screen)
if self._ui_screen.password_func:
handler.set_pass_func(self._ui_screen.password_func)
handler.set_callback(self.process_input)
handler.get_input(prompt)
def _is_input_expected(self, prompt):
"""Check if user handled input processing some other way.
Do nothing if user did handled user input.
:returns: True if prompt is set and we can use it to get user input.
False if prompt is not available, which means that user handled input on their
own.
"""
# None means prompt handled the input by itself -> continue
if prompt is None:
self._input_error_counter = 0
return False
return True
def process_input(self, user_input):
"""Process input from the screens.
:param user_input: User input string.
:type user_input: String.
:raises: ExitMainLoop or any other kind of exception from screen processing.
"""
# process the input, if it wasn't processed (valid)
# increment the error counter
try:
result = self._process_input(user_input)
except ExitMainLoop: # pylint: disable=try-except-raise
raise
except Exception: # pylint: disable=broad-except
App.get_event_loop().enqueue_signal(ExceptionSignal(self))
return
if result.was_successful():
self._input_error_counter = 0
else:
self._input_error_counter += 1
App.get_scheduler().process_input_result(result, self.input_error_threshold_exceeded)
def _process_input(self, key):
"""Method called internally to process unhandled input key presses.
:param key: The string entered by user.
:type key: String.
:return: Return state result object.
:rtype: `simpleline.render.in_out_manager.UserInputResult` class.
:raises: Anything the Screen can raise in the input processing.
"""
from simpleline.render.screen import InputState # pylint: disable=import-outside-toplevel
# delegate the handling to active screen first
key = self._ui_screen.input(self._input_args, key)
if key == InputState.PROCESSED:
return UserInputAction.NOOP
if key == InputState.PROCESSED_AND_REDRAW:
return UserInputAction.REDRAW
if key == InputState.PROCESSED_AND_CLOSE:
return UserInputAction.CLOSE
if key == InputState.DISCARDED:
return UserInputAction.INPUT_ERROR
# global refresh command
if key == Prompt.REFRESH:
return UserInputAction.REDRAW
# global close command
if key == Prompt.CONTINUE:
return UserInputAction.CLOSE
# global quit command
if key == Prompt.QUIT:
return UserInputAction.QUIT
if key is None:
log.warning("Returned key from screen is None. "
"This could be missing return in a screen input method?")
return UserInputAction.INPUT_ERROR
class UserInputAction(Enum):
"""Store user input result."""
INPUT_ERROR = -1
NOOP = 0
REDRAW = 5
CLOSE = 6
QUIT = 7
def was_successful(self):
return self != UserInputAction.INPUT_ERROR

Some files were not shown because too many files have changed in this diff Show More