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
+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
@@ -0,0 +1,94 @@
# Signal handler is giving ability connect and emit to all 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 simpleline import App
from simpleline.event_loop.signals import RenderScreenSignal, CloseScreenSignal
class SignalHandler():
"""Provides methods for handling signals anc callbacks.
Provides main methods:
`connect()` -- connect this widget on given signal
`create_signal()` -- create signal class which can be used in the emit method
`emit()` -- emit signal given signal
"""
def connect(self, signal, callback, data=None):
"""Connect this class method with given signal.
:param signal: signal class which you want to connect
:type signal: class based on `simpleline.event_loop.AbstractSignal`
:param callback: the callback function
:type callback: func(event_message, data)
:param data: Data you want to pass to the callback
:type data: Anything
"""
App.get_event_loop().register_signal_handler(signal, callback, data)
def create_signal(self, signal_class, priority=0):
"""Create signal instance usable in the emit method.
:param signal_class: signal you want to use
:type signal_class: class based on `simpleline.event_loop.AbstractSignal`
:param priority: priority of the signal; please look on the
`simpleline.event_loop.AbstractSignal.priority` for further info
:type priority: int
"""
return signal_class(self, priority)
def emit(self, signal):
"""Emit the signal.
This will add `signal` to the event loop.
:param signal: signal to emit
:type signal: instance of class based on `simpleline.event_loop.AbstractSignal`
"""
App.get_event_loop().enqueue_signal(signal)
def create_and_emit(self, signal):
"""Create the signal and emit it.
This is basically shortcut for calling `self.create_signal` and `self.emit`.
"""
created_signal = self.create_signal(signal)
self.emit(created_signal)
def redraw(self):
"""Emit signal to initiate draw.
Add RenderScreenSignal to the event loop.
"""
signal = self.create_signal(RenderScreenSignal)
App.get_event_loop().enqueue_signal(signal)
def close(self):
"""Emit signal to close this screen.
Add CloseScreenSignal to the event loop.
"""
signal = self.create_signal(CloseScreenSignal)
App.get_event_loop().enqueue_signal(signal)
@@ -0,0 +1,58 @@
# Serves shortcuts for easy screen scheduling.
#
# 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 import App
class ScreenHandler():
@classmethod
def schedule_screen(cls, ui_screen, args=None):
"""Schedule screen to the active scheduler.
See: `simpleline.render.screen_scheduler.schedule_screen()`.
"""
App.get_scheduler().schedule_screen(ui_screen=ui_screen, args=args)
@classmethod
def replace_screen(cls, ui_screen, args=None):
"""Schedule screen to the active scheduler.
See: `simpleline.render.screen_scheduler.replace_screen()`.
"""
App.get_scheduler().replace_screen(ui_screen=ui_screen, args=args)
@classmethod
def push_screen(cls, ui_screen, args=None):
"""Schedule screen to the active scheduler.
See: `simpleline.render.screen_scheduler.push_screen()`.
"""
App.get_scheduler().push_screen(ui_screen=ui_screen, args=args)
@classmethod
def push_screen_modal(cls, ui_screen, args=None):
"""Schedule screen to the active scheduler.
See: `simpleline.render.screen_scheduler.push_screen_modal()`.
"""
App.get_scheduler().push_screen_modal(ui_screen=ui_screen, args=args)
@@ -0,0 +1,311 @@
# Class handling rendering of the screens to console.
#
# 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 threading
from simpleline import App
from simpleline.event_loop import ExitMainLoop
from simpleline.event_loop.signals import ExceptionSignal, RenderScreenSignal, CloseScreenSignal
from simpleline.render import RenderUnexpectedError
from simpleline.render.screen.input_manager import UserInputAction
from simpleline.render.screen_stack import ScreenStack, ScreenData, ScreenStackEmptyException
from simpleline.logging import get_simpleline_logger
log = get_simpleline_logger()
RAW_INPUT_LOCK = threading.Lock()
__all__ = ["ScreenScheduler"]
class ScreenScheduler():
def __init__(self, event_loop, scheduler_stack=None):
"""Constructor where you can pass your own scheduler stack.
The ScreenStack will be used automatically if scheduler stack will be None.
:param event_loop: Event loop used for the scheduler.
:type event_loop: Class based on `simpleline.event_loop.AbstractEventLoop`.
:param scheduler_stack: Use custom scheduler stack if you need to.
:type scheduler_stack: `simpleline.screen_stack.ScreenStack` based class.
"""
self._quit_screen = None
self._event_loop = event_loop
if scheduler_stack:
self._screen_stack = scheduler_stack
else:
self._screen_stack = ScreenStack()
self._register_handlers()
self._first_screen_scheduled = False
@staticmethod
def _spacer():
return "\n".join(2 * [App.get_configuration().width * "="])
def _register_handlers(self):
self._event_loop.register_signal_handler(RenderScreenSignal, self._process_screen_callback)
self._event_loop.register_signal_handler(CloseScreenSignal, self._close_screen_callback)
@property
def quit_screen(self):
"""Return quit UIScreen."""
return self._quit_screen
@quit_screen.setter
def quit_screen(self, quit_screen):
"""Set the UIScreen based instance which will be showed before the Application will quit.
You can also use `simpleline.render.adv_widgets.YesNoDialog` or `UIScreen` based class
with the `answer` property. Without the `answer` property the application will always
close.
"""
self._quit_screen = quit_screen
@property
def nothing_to_render(self):
"""Is something for rendering in the scheduler stack?
:return: True if the rendering stack is empty
:rtype: bool
"""
return self._screen_stack.empty()
def dump_stack(self):
"""Get string representation of actual screen stack."""
return self._screen_stack.dump_stack()
def schedule_screen(self, ui_screen, args=None):
"""Add screen to the bottom of the stack.
This is mostly useful at the beginning to prepare the first screen hierarchy to display.
:param ui_screen: screen to show
:type ui_screen: UIScreen instance
:param args: optional argument, please see switch_screen for details
:type args: anything
"""
log.debug("Scheduling screen %s", ui_screen)
screen = ScreenData(ui_screen, args)
self._screen_stack.add_first(screen)
self._redraw_on_first_scheduled_screen()
def _redraw_on_first_scheduled_screen(self):
if not self._first_screen_scheduled:
self.redraw()
self._first_screen_scheduled = True
def replace_screen(self, ui_screen, args=None):
"""Schedules a screen to replace the current one.
:param ui_screen: screen to show
:type ui_screen: instance of UIScreen
:param args: optional argument to pass to ui's refresh and setup methods
(can be used to select what item should be displayed or so)
:type args: anything
"""
log.debug("Replacing screen %s", ui_screen)
try:
execute_new_loop = self._screen_stack.pop().execute_new_loop
except ScreenStackEmptyException as e:
raise ScreenStackEmptyException("Switch screen is not possible when there is no "
"screen scheduled!") from e
# we have to keep the old_loop value so we stop
# dialog's mainloop if it ever uses switch_screen
screen = ScreenData(ui_screen, args, execute_new_loop)
self._screen_stack.append(screen)
self.redraw()
def push_screen(self, ui_screen, args=None):
"""Schedules a screen to show, but keeps the current one in stack to
return to, when the new one is closed.
:param ui_screen: screen to show
:type ui_screen: UIScreen instance
:param args: optional argument
:type args: anything
"""
log.debug("Pushing screen %s to stack", ui_screen)
screen = ScreenData(ui_screen, args, False)
self._screen_stack.append(screen)
self.redraw()
def push_screen_modal(self, ui_screen, args=None):
"""Starts a new screen right away, so the caller can collect data back.
When the new screen is closed, the caller is redisplayed.
This method does not return until the new screen is closed.
:param ui_screen: screen to show
:type ui_screen: UIScreen instance
:param args: optional argument, please see switch_screen for details
:type args: anything
"""
log.debug("Pushing modal screen %s to stack", ui_screen)
screen = ScreenData(ui_screen, args, True)
self._screen_stack.append(screen)
# only new events will be processed now
# the old one will wait after this event loop will be closed
self._event_loop.execute_new_loop(RenderScreenSignal(self))
def _close_screen_callback(self, signal, data):
self.close_screen(signal.source)
def close_screen(self, closed_from=None):
"""Close the currently displayed screen and exit it's main loop if necessary.
Next screen from the stack is then displayed.
"""
screen = self._screen_stack.pop()
log.debug("Closing screen %s from %s", screen, closed_from)
# User can react when screen is closing
screen.ui_screen.closed()
if closed_from is not None and closed_from is not screen.ui_screen:
raise RenderUnexpectedError("You are trying to close screen %s from screen %s! "
"This is most probably not intentional." %
(closed_from, screen.ui_screen))
if screen.execute_new_loop:
self._event_loop.close_loop()
# redraw screen if there is what to redraw
# and if it is not modal screen (modal screen parent is blocked)
if not self._screen_stack.empty() and not screen.execute_new_loop:
self.redraw()
# we can't draw anything more. Kill the application.
if self._screen_stack.empty():
raise ExitMainLoop()
def redraw(self):
"""Register rendering to the event loop for processing."""
self._event_loop.enqueue_signal(RenderScreenSignal(self))
def _process_screen_callback(self, signal, data):
self._process_screen()
def _process_screen(self):
"""Process the current screen.
1) It will call setup if the screen is not already set.
2a) If setup was success then draw the screen.
2b) If setup wasn't successful then pop the screen and try to process next in the stack.
Continue by (1).
3)Ask for user input if requested.
"""
top_screen = self._get_last_screen()
log.debug("Processing screen %s", top_screen)
# this screen is used first time (call setup() method)
if not top_screen.ui_screen.screen_ready:
if not top_screen.ui_screen.setup(top_screen.args):
# remove the screen and skip if setup went wrong
self._screen_stack.pop()
self.redraw()
log.warning("Screen %s setup wasn't successful", top_screen)
return
# get the widget tree from the screen and show it in the screen
try:
# refresh screen content
top_screen.ui_screen.refresh(top_screen.args)
# Screen was closed in the refresh method
if top_screen != self._get_last_screen():
return
# draw screen to the console
self._draw_screen(top_screen)
if top_screen.ui_screen.input_required:
log.debug("Input is required by %s screen", top_screen)
top_screen.ui_screen.get_input_with_error_check(top_screen.args)
except ExitMainLoop: # pylint: disable=try-except-raise
raise
except Exception: # pylint: disable=broad-except
self._event_loop.enqueue_signal(ExceptionSignal(self))
return
def _draw_screen(self, active_screen):
"""Draws the current `active_screen`.
:param active_screen: Screen which should be draw to the console.
:type active_screen: Classed based on `simpleline.render.screen.UIScreen`.
"""
# get the widget tree from the screen and show it in the screen
try:
if not active_screen.ui_screen.no_separator:
# separate the content on the screen from the stuff we are about to display now
print(self._spacer())
# print UIScreen content
active_screen.ui_screen.show_all()
except ExitMainLoop: # pylint: disable=try-except-raise
raise
except Exception: # pylint: disable=broad-except
self._event_loop.enqueue_signal(ExceptionSignal(self))
def _get_last_screen(self):
if self._screen_stack.empty():
raise ExitMainLoop()
return self._screen_stack.pop(False)
def process_input_result(self, input_result, should_redraw):
active_screen = self._get_last_screen()
if not input_result.was_successful():
if should_redraw:
self.redraw()
else:
log.debug("Input was not successful, ask for new input.")
active_screen.ui_screen.get_input_with_error_check(active_screen.args)
else:
if input_result == UserInputAction.NOOP:
return
if input_result == UserInputAction.REDRAW:
self.redraw()
elif input_result == UserInputAction.CLOSE:
self.close_screen()
elif input_result == UserInputAction.QUIT:
if self.quit_screen:
self.push_screen_modal(self.quit_screen)
try:
if self.quit_screen.answer is True:
raise ExitMainLoop()
self.redraw()
except AttributeError as e:
raise ExitMainLoop() from e
else:
raise ExitMainLoop()
@@ -0,0 +1,117 @@
# Classes implementation for storing and manipulating Screen stack.
#
# 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 ScreenStackException(SimplelineError):
"""General screen stack exception."""
class ScreenStackEmptyException(ScreenStackException):
"""Screen stack exception when stack is empty."""
class ScreenStack():
"""Managing screen stack used in `ScreenScheduler`."""
def __init__(self):
self._screens = []
def empty(self):
"""Test if screen stack is empty.
:return: True if empty.
:rtype: bool
"""
return not self._screens
def size(self):
"""Get size of the stack.
:return: Size of the stack.
"""
return len(self._screens)
def append(self, screen):
"""Add new screen to the top of the stack.
:param screen: Screen for the future rendering.
:type screen: Class based on `simpleline.render.ui_screen.UIScreen`.
"""
self._screens.append(screen)
def pop(self, remove=True):
"""Return top item from the stack.
:param remove: If True (default) also remove this items from the stack.
:return: The top screen on the stack.
"""
try:
if remove:
return self._screens.pop()
return self._screens[-1]
except IndexError as e:
raise ScreenStackEmptyException(e) from e
def add_first(self, screen):
"""Add `screen` to the bottom of the stack.
:param screen: Add the `screen` to the bottom of the stack.
:type screen: Class based on `simpleline.render.ui_screen.UIScreen`.
"""
self._screens.insert(0, screen)
def dump_stack(self):
"""Dump screen stack structure.
:returns: Screen stack representation.
:rtype: str
"""
msg = '======= Screen stack =======\n'
msg += '----------- TOP ------------\n'
for screen in reversed(self._screens):
msg += str(screen)
msg += "\n"
msg += '============================\n'
return msg
class ScreenData():
"""Inner data class to store screen data."""
def __init__(self, ui_screen, args=None, execute_new_loop=False):
self.ui_screen = ui_screen
self.args = args
self.execute_new_loop = execute_new_loop
def __str__(self):
msg = self.__class__.__name__
msg += "("
msg += ",".join((str(self.ui_screen), str(self.args), str(self.execute_new_loop)))
msg += ")"
return msg
@@ -0,0 +1,495 @@
# Widgets for 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/>.
#
import functools
from textwrap import wrap
from simpleline.utils.i18n import _
from simpleline.utils import ensure_str
__all__ = ["Widget", "TextWidget", "SeparatorWidget", "EntryWidget", "ColumnWidget",
"CheckboxWidget", "CenterWidget"]
class Widget():
def __init__(self, max_width=None, default=None):
"""Initializes base Widgets buffer.
This class can be subclassed to create customized widgets.
:param max_width: serves as a hint about screen size to write method with default arguments
:type max_width: int
:param default: string containing the default content to fill the buffer with
:type default: string
"""
self._buffer = []
if default:
self._buffer = [[c for c in l] for l in default.split("\n")] # pylint: disable=unnecessary-comprehension
self._max_width = max_width
self._cursor = (0, 0) # row, col
@property
def height(self):
"""The current height of the internal buffer."""
return len(self._buffer)
@property
def width(self):
"""The current width of the internal buffer (id of the first empty column)."""
return functools.reduce(lambda acc, l: max(acc, len(l)), self._buffer, 0)
def clear(self):
"""Clears this widgets buffer and resets cursor."""
self._buffer = list()
self._cursor = (0, 0)
@property
def content(self):
"""Return a list (rows) of lists (columns) with one character elements."""
return self._buffer
def render(self, width):
"""Redraw the widget's self._buffer.
:param width: the width of buffer requested by the caller
:type width: int
Commonly, call render of child widgets and then draw and write
methods to copy their contents to self._buffer.
"""
self.clear()
def get_lines(self):
"""Return lines to write out in order to show this widget.
:return: lines representing this widget
:rtype: list(str)
"""
return [str(u"".join(line)) for line in self._buffer]
def set_cursor_position(self, row, col):
"""Set cursor position.
:param row: row id, starts with 0 at the top of the screen
:type row: int
:param col: column id, starts with 0 on the left side of the screen
:type col: int
"""
self._cursor = (row, col)
@property
def cursor(self):
return self._cursor
def set_end(self):
"""Set the cursor to first column in new line at the end."""
self._cursor = (self.height, 0)
def draw(self, w, row=None, col=None, block=False):
"""Copy w widget's content to this widget's buffer at row, col position.
:param w: widget to take content from
:type w: class Widget
:param row: row number to start at (default is at the cursor position)
:type row: int
:param col: column number to start at (default is at the cursor position)
:type col: int
:param block: when printing newline, start at column col (True) or at column 0 (False)
:type block: boolean
"""
# if the starting row is not present, start at the cursor position
if row is None:
row = self._cursor[0]
# if the starting column is not present, start at the cursor position
if col is None:
col = self._cursor[1]
# fill up rows to accommodate for w.height
if self.height < row + w.height:
for _i in range(row + w.height - self.height):
self._buffer.append(list())
# append columns to accommodate for w.width
for l in range(row, row + w.height):
l_len = len(self._buffer[l])
w_len = len(w.content[l - row])
if l_len < col + w_len:
self._buffer[l] += ((col + w_len - l_len) * list(u" "))
self._buffer[l][col:col + w_len] = w.content[l - row][:]
# move the cursor to new spot
if block:
self._cursor = (row + w.height, col)
else:
self._cursor = (row + w.height, 0)
def write(self, text, row=None, col=None, width=None, block=False, wordwrap=False):
"""Emulate the typing machine writing to this widget's buffer.
:param text: text to type
:type text: str
:param row: row number to start at (default is at the cursor position)
:type row: int
:param col: column number to start at (default is at the cursor position)
:type col: int
:param width: wrap at "col" + "width" column (default is at self._max_width)
:type width: int
:param block: when printing newline, start at column col (True) or at column 0 (False)
:type block: boolean
:param wordwrap: wrap by words
:type wordwrap: boolean
"""
if not text:
return
text = ensure_str(text)
if row is None:
row = self._cursor[0]
if col is None:
col = self._cursor[1]
if width is None and self._max_width:
width = self._max_width - col
x = row
y = col
if wordwrap:
text = self._wrap_words(text, width)
# emulate typing machine
for character in text:
# FIXME: Remove the code duplication below and optimize it
# process newline
if character == "\n":
x += 1
if block:
y = col
else:
y = 0
self._increase_x_buffer_size(x)
continue
self._increase_x_buffer_size(x)
self._increase_y_buffer_size(x, y)
self._save_character_to_buffer(x, y, character)
# shift to the next char
y += 1
if width is not None and y >= col + width:
x += 1
if block:
y = col
else:
y = 0
self._cursor = (x, y)
def _increase_x_buffer_size(self, x):
if x >= len(self._buffer):
for _i in range(x - len(self._buffer) + 1):
self._buffer.append(list())
def _increase_y_buffer_size(self, x, y):
if y >= len(self._buffer[x]):
self._buffer[x] += ((y - len(self._buffer[x]) + 1) * list(u" "))
def _save_character_to_buffer(self, x, y, character):
self._buffer[x][y] = character
@staticmethod
def _wrap_words(text, width):
lines = []
# Wrap each line separately
for line in text.split('\n'):
sublines = []
for subline in wrap(line, width):
sublines.append(subline)
if len(subline) < width:
# line shorter than width will be wrapped by '\n' we add
sublines.append('\n')
# line with length == width will be wrapped by the width based
# wrapping logic
# end of line will be wrapped by '\n' following the line in
# original text
if sublines and sublines[-1] == '\n':
sublines.pop()
lines.append("".join(sublines))
return '\n'.join(lines)
class TextWidget(Widget):
"""Class to handle wrapped text output."""
def __init__(self, text):
"""
:param text: text to format
:type text: str
"""
super().__init__()
self._text = text
@property
def text(self):
"""Contains text of this widget."""
return self._text
def render(self, width):
"""Renders the text widget limited to width number of columns.
Wraps to the next line when the text is longer.
:param width: maximum width allocated to the string
:type width: int
"""
super().render(width)
self.write(self._text, width=width, wordwrap=True)
class EntryWidget(TextWidget):
"""This is the easy way how to generate entry items for containers.
If the numbering in a container is turned on the output looks like:
N) title
value
Without numbering turned on:
title
value
"""
def __init__(self, title, value=None):
""" Create Entry widget instance.
:param title: Title of this entry.
:type title: String.
:param value: Actual value printed in second line below the title.
:type value: String.
"""
text = self._create_text(title=title, value=value)
super().__init__(text)
@staticmethod
def _create_text(title, value):
msg = title
if value:
msg += "\n"
msg += value
return msg
class SeparatorWidget(Widget):
"""Print empty line."""
def __init__(self, lines=1):
"""Construct SeparatorWidget for printing blank lines.
:param lines: How many lines should be blank.
:type lines: int greater than 0.
"""
super().__init__()
self._lines = lines
def render(self, width):
"""Render empty line to the buffer.
:param width: maximum width allocated to the string
:type width: int
"""
super().render(width)
self.write("")
def write(self, text, row=None, col=None, width=None, block=False, wordwrap=False):
"""Optimize write function.
To print just a blank line we don't need too much logic.
"""
for i in range(0, self._lines):
self._buffer.append(list())
self._buffer[i] += u""
self.set_cursor_position(self._lines - 1, 0)
class CenterWidget(Widget):
"""Class to handle horizontal centering of content."""
def __init__(self, w):
"""
:param w: widget to center
:type w: Widget
"""
super().__init__()
self._w = w
def render(self, width):
"""Render the centered widget to internal buffer.
:param width: maximum width the widget should use
:type width: int
"""
super().render(width)
self._w.render(width)
# make sure col is an integer
self.draw(self._w, col=(width - self._w.width) // 2)
class CheckboxWidget(Widget):
"""Widget to show checkbox with (un)checked box, name and description."""
def __init__(self, key="x", title=None, text=None, completed=None):
"""
:param key: tick character to be used inside [ ]
:type key: character
:param title: the title next to the [ ] box
:type title: str
:param text: the description text to be shown on the second row in ()
:type text: str
:param completed: is the checkbox ticked or not?
:type completed: True|False
"""
super().__init__()
self._key = key
self._title = title
self._text = text
self._completed = completed
def render(self, width):
"""Render the widget to internal buffer.
It should be max width characters wide.
"""
super().render(width)
if self.completed:
checkchar = self._key
else:
checkchar = " "
# prepare the checkbox
checkbox = TextWidget("[%s]" % checkchar)
data = []
# append lines
if self.title:
data.append(TextWidget(_(self.title)))
if self.text:
data.append(TextWidget("(%s)" % self.text))
# the checkbox has two columns
# [x] is one and is 3 chars wide
# text is second and can occupy width - 3 - 1 (for space) chars
cols = ColumnWidget([(3, [checkbox]), (width - 4, data)], 1)
cols.render(width)
# transfer the column widget rendered stuff to internal buffer
self.draw(cols)
@property
def title(self):
"""Returns the first line (main title) of the checkbox."""
return self._title
@property
def completed(self):
"""Returns the state of the checkbox, checked is True."""
return self._completed
@property
def text(self):
"""Contains the description text from the second line."""
return self._text
class ColumnWidget(Widget):
def __init__(self, columns, spacing=0):
"""Create text columns
Deprecated. Please do not use this widget, use containers instead.
:param columns: list containing (column width, [list of widgets to put into this column])
:type columns: [(int, [...]), ...]
:param spacing: number of spaces to use between columns
:type spacing: int
"""
super().__init__()
self._spacing = spacing
self._columns = columns
def render(self, width):
"""Render the widget to it's internal buffer
:param width: the maximum width the widget can use
:type width: int
:return: nothing
"""
super().render(width)
# the leftmost empty column
col_pos = 0
# iterate over tuples (column width, column content)
for col_width, col in self._columns:
# set cursor to first line and leftmost empty column
self.set_cursor_position(0, col_pos)
# if requested width is None, limit the maximum to width
# and set minimum to 0
if col_width is None:
col_max_width = width - self.cursor[1]
col_width = 0
else:
col_max_width = col_width
# render and draw contents of column
for item in col:
item.render(col_max_width)
self.draw(item, block=True)
# recompute the leftmost empty column
col_pos = max((col_pos + col_width), self.width) + self._spacing
@@ -0,0 +1,94 @@
# iutil.py - generic install utility functions
#
# 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
import string # pylint: disable=deprecated-module
import unicodedata
def ensure_str(str_or_bytes, keep_none=True):
"""
Returns a str instance for given string or ``None`` if requested to keep it.
:param str_or_bytes: string to be kept or converted to str type
:type str_or_bytes: str or bytes
:param bool keep_none: whether to keep None as it is or raise ValueError if
``None`` is passed
:raises ValueError: if applied on an object not being of type bytes nor str
(nor NoneType if ``keep_none`` is ``False``)
"""
if keep_none and str_or_bytes is None:
return None
if isinstance(str_or_bytes, str):
return str_or_bytes
if isinstance(str_or_bytes, bytes):
return str_or_bytes.decode(sys.getdefaultencoding())
raise ValueError(
"str_or_bytes must be of type 'str' or 'bytes', not '%s'" % type(str_or_bytes))
# Define translations between ASCII uppercase and lowercase for
# locale-independent string conversions. The tables are 256-byte string used
# with str.translate. If str.translate is used with a unicode string,
# even if the string contains only 7-bit characters, str.translate will
# raise a UnicodeDecodeError.
_ASCIIlower_table = str.maketrans(string.ascii_uppercase, string.ascii_lowercase)
_ASCIIupper_table = str.maketrans(string.ascii_lowercase, string.ascii_uppercase)
def _toASCII(s):
"""Convert a unicode string to ASCII"""
if isinstance(s, str):
# Decompose the string using the NFK decomposition, which in addition
# to the canonical decomposition replaces characters based on
# compatibility equivalence (e.g., ROMAN NUMERAL ONE has its own code
# point but it's really just a capital I), so that we can keep as much
# of the ASCII part of the string as possible.
s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode("ascii")
elif not isinstance(s, bytes):
s = ''
return s
def lowerASCII(s):
"""Convert a string to lowercase using only ASCII character definitions.
The returned string will contain only ASCII characters. This function is
locale-independent.
"""
# XXX: Python 3 has str.maketrans() and bytes.maketrans() so we should
# ideally use one or the other depending on the type of 's'. But it turns
# out we expect this function to always return string even if given bytes.
s = ensure_str(s)
return str.translate(_toASCII(s), _ASCIIlower_table)
def upperASCII(s):
"""Convert a string to uppercase using only ASCII character definitions.
The returned string will contain only ASCII characters. This function is
locale-independent.
"""
# XXX: Python 3 has str.maketrans() and bytes.maketrans() so we should
# ideally use one or the other depending on the type of 's'. But it turns
# out we expect this function to always return string even if given bytes.
s = ensure_str(s)
return str.translate(_toASCII(s), _ASCIIupper_table)
@@ -0,0 +1,61 @@
# Translation functions we use all over the place
#
# 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__ = ["_", "N_", "P_", "C_", "CN_", "CP_"]
import gettext
N_ = lambda x: x
_ = lambda x: gettext.translation("python-simpleline", fallback=True).gettext(x) if x != "" else ""
P_ = lambda x, y, z: gettext.translation("python-simpleline", fallback=True).ngettext(x, y, z)
# This is equivalent to "pgettext" in GNU gettext. The pgettext functions
# are not exported by Python, but all they really do is a stick a EOT
# character between msgctxt and msgid and check that msgctxt isn't part
# of the return value.
def C_(msgctxt, msgid):
ctxid = "%s\x04%s" % (msgctxt, msgid)
translation = _(ctxid)
# If there is no translation for msgctxt<EOT>msgid, return only msgid
if translation == ctxid:
return msgid
return translation
# Mark as translatable with context
CN_ = lambda c, x: x
# npgettext; i.e., gettext with plural form and context
def CP_(msgctxt, msgid, msgid_plural, n):
ctxid = "%s\x04%s" % (msgctxt, msgid)
translation = P_(ctxid, msgid_plural, n)
# If the returned value is msgctxt<EOT>msgid, ngettext was trying to
# fallback to msgid. We don't add msgctxt to msgid_plural, so any other
# return value is correct.
if translation == ctxid:
return msgid
return translation
@@ -0,0 +1,157 @@
### GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the
terms and conditions of version 3 of the GNU General Public License,
supplemented by the additional permissions listed below.
#### 0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the
GNU General Public License.
"The Library" refers to a covered work governed by this License, other
than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
#### 1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
#### 2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
- a) under this License, provided that you make a good faith effort
to ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
- b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
#### 3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a
header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
- a) Give prominent notice with each copy of the object code that
the Library is used in it and that the Library and its use are
covered by this License.
- b) Accompany the object code with a copy of the GNU GPL and this
license document.
#### 4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken
together, effectively do not restrict modification of the portions of
the Library contained in the Combined Work and reverse engineering for
debugging such modifications, if you also do each of the following:
- a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
- b) Accompany the Combined Work with a copy of the GNU GPL and this
license document.
- c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
- d) Do one of the following:
- 0) Convey the Minimal Corresponding Source under the terms of
this License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
- 1) Use a suitable shared library mechanism for linking with
the Library. A suitable mechanism is one that (a) uses at run
time a copy of the Library already present on the user's
computer system, and (b) will operate properly with a modified
version of the Library that is interface-compatible with the
Linked Version.
- e) Provide Installation Information, but only if you would
otherwise be required to provide such information under section 6
of the GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the Application
with a modified version of the Linked Version. (If you use option
4d0, the Installation Information must accompany the Minimal
Corresponding Source and Corresponding Application Code. If you
use option 4d1, you must provide the Installation Information in
the manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.)
#### 5. Combined Libraries.
You may place library facilities that are a work based on the Library
side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
- a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities, conveyed under the terms of this License.
- b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
#### 6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser 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 Library
as you received it specifies that a certain numbered version of the
GNU Lesser General Public License "or any later version" applies to
it, you have the option of following the terms and conditions either
of that published version or of any later version published by the
Free Software Foundation. If the Library as you received it does not
specify a version number of the GNU Lesser General Public License, you
may choose any version of the GNU Lesser General Public License ever
published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
@@ -0,0 +1 @@
/home/nocode/Files/FWS-ISO/pkgbuilds/python-simpleline/python-simpleline-1.8.tar.gz
@@ -0,0 +1,16 @@
name: validate
on: [push, pull_request]
jobs:
rawhide:
runs-on: ubuntu-latest
container:
image: registry.fedoraproject.org/fedora:rawhide
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: install test dependencies
run: dnf install -y python3-pylint python3-gobject-base make
- name: run tests
run: make ci
@@ -0,0 +1,23 @@
*.pyc
.*.swp
.coverage
tests/coverage-report.log
ChangeLog
po/*.gmo
po/*.po
po/python-simpleline.pot
po/POTFILES
tests/pylint/.pylint.d/
pylint-log
build/
dist/
simpleline.egg-info/
.directory
doc/_build
@@ -0,0 +1,46 @@
specfile_path: python-simpleline.spec
upstream_package_name: simpleline
upstream_tag_template: simpleline-{version}
actions:
create-archive:
- "make BUILD_ARGS=sdist archive"
- 'bash -c "cp dist/*.tar.gz ."'
- 'bash -c "ls *.tar.gz"'
jobs:
- job: propose_downstream
trigger: release
metadata:
dist_git_branches: fedora-development
- job: tests
trigger: pull_request
metadata:
targets:
- fedora-all
- job: copr_build
trigger: pull_request
metadata:
targets:
- fedora-eln
- job: copr_build
trigger: commit
metadata:
targets:
- fedora-rawhide
- fedora-eln
branch: master
owner: "@rhinstaller"
project: Anaconda
preserve_project: True
- job: copr_build
trigger: commit
metadata:
targets:
- fedora-34
branch: master
owner: "@rhinstaller"
project: Anaconda-devel
preserve_project: True
@@ -0,0 +1,504 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=0
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Specify a configuration file.
#rcfile=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=fixme,
invalid-name,
missing-docstring,
no-self-use,
too-few-public-methods,
too-many-arguments,
too-many-instance-attributes,
too-many-public-methods,
too-many-return-statements,
unnecessary-pass,
unused-argument,
useless-return,
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=c-extension-no-member
[REPORTS]
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template='{msg_id}({symbol}):{path}:{line},{column}: {obj}: {msg}'
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=colorized
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=no
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=99
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[LOGGING]
# Format style used to check logging format string. `old` means using %
# formatting, while `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: en_US (myspell), cs_CZ
# (myspell), en_GB (myspell), en_AG (myspell), en_AU (myspell), en_BS
# (myspell), en_BW (myspell), en_BZ (myspell), en_CA (myspell), en_DK
# (myspell), en_GH (myspell), en_HK (myspell), en_IE (myspell), en_IN
# (myspell), en_JM (myspell), en_MW (myspell), en_NA (myspell), en_NG
# (myspell), en_NZ (myspell), en_PH (myspell), en_SG (myspell), en_TT
# (myspell), en_ZA (myspell), en_ZM (myspell), en_ZW (myspell)..
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether the implicit-str-concat-in-sequence should
# generate a warning on implicit string concatenation in sequences defined over
# several lines.
check-str-concat-over-line-jumps=no
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=DefaultInstall,Popen,QueueFactory,TransactionSet,Namespace
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
# callbacks=cb_,
# _cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=yes
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[DESIGN]
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement.
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[IMPORTS]
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix,string,regsub,TERMINOS,Bastion,rexec,mock
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception
@@ -0,0 +1,157 @@
### GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the
terms and conditions of version 3 of the GNU General Public License,
supplemented by the additional permissions listed below.
#### 0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the
GNU General Public License.
"The Library" refers to a covered work governed by this License, other
than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
#### 1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
#### 2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
- a) under this License, provided that you make a good faith effort
to ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
- b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
#### 3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a
header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
- a) Give prominent notice with each copy of the object code that
the Library is used in it and that the Library and its use are
covered by this License.
- b) Accompany the object code with a copy of the GNU GPL and this
license document.
#### 4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken
together, effectively do not restrict modification of the portions of
the Library contained in the Combined Work and reverse engineering for
debugging such modifications, if you also do each of the following:
- a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
- b) Accompany the Combined Work with a copy of the GNU GPL and this
license document.
- c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
- d) Do one of the following:
- 0) Convey the Minimal Corresponding Source under the terms of
this License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
- 1) Use a suitable shared library mechanism for linking with
the Library. A suitable mechanism is one that (a) uses at run
time a copy of the Library already present on the user's
computer system, and (b) will operate properly with a modified
version of the Library that is interface-compatible with the
Linked Version.
- e) Provide Installation Information, but only if you would
otherwise be required to provide such information under section 6
of the GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the Application
with a modified version of the Linked Version. (If you use option
4d0, the Installation Information must accompany the Minimal
Corresponding Source and Corresponding Application Code. If you
use option 4d1, you must provide the Installation Information in
the manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.)
#### 5. Combined Libraries.
You may place library facilities that are a work based on the Library
side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
- a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities, conveyed under the terms of this License.
- b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
#### 6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser 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 Library
as you received it specifies that a certain numbered version of the
GNU Lesser General Public License "or any later version" applies to
it, you have the option of following the terms and conditions either
of that published version or of any later version published by the
Free Software Foundation. If the Library as you received it does not
specify a version number of the GNU Lesser General Public License, you
may choose any version of the GNU Lesser General Public License ever
published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
@@ -0,0 +1,4 @@
include LICENSE.md Makefile ChangeLog README.md
recursive-include simpleline *.py
recursive-include po *.po *.pot Makefile
recursive-include tests *.py *.sh
@@ -0,0 +1,154 @@
# 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/>.
PKGNAME=simpleline
SPECNAME=python-$(PKGNAME)
VERSION=$(shell awk '/Version:/ { print $$2 }' $(SPECNAME).spec)
RELEASE=$(shell awk '/Release:/ { print $$2 }' $(SPECNAME).spec | sed -e 's|%.*$$||g')
TAG=$(PKGNAME)-$(VERSION)
PYTHON?=python3
COVERAGE?=coverage3
# Arguments used for setup.py call for creating archive
BUILD_ARGS ?= sdist bdist_wheel
# LOCALIZATION SETTINGS
L10N_REPOSITORY ?= https://github.com/rhinstaller/python-simpleline-l10n.git
L10N_REPOSITORY_RW ?= git@github.com:rhinstaller/python-simpleline-l10n.git
# Branch used in localization repository. This should be master all the time.
GIT_L10N_BRANCH ?= master
# Directory in localization repository specific for this branch.
L10N_DIR ?= master
default: all
.PHONY: all
all:
$(MAKE) -C po
.PHONY: clean
clean:
-rm -rf *.tar.gz simpleline/*.pyc tests/*.pyc ChangeLog dist build simpleline.egg-info
$(MAKE) -C po clean
$(PYTHON) setup.py -q clean --all
.PHONY: test
test:
@echo "*** Running unittests ***"
./tests/units/run_test.sh
.PHONY: coverage
coverage:
@echo "*** Running unittests with coverage ***"
PYTHON="$(COVERAGE) run --branch" ./tests/units/run_test.sh
$(COVERAGE) report -m --include="simpleline/*" | tee tests/coverage-report.log
.PHONY: check
check:
@echo "*** Running pylint ***"
$(PYTHON) -m pylint simpleline/ examples/*/*.py tests/units/
.PHONY: install
install:
$(PYTHON) setup.py install --root=$(DESTDIR)
$(MAKE) -C po install
.PHONY: ChangeLog
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)
.PHONY: tag
tag:
git tag -a -m "Tag as $(TAG)" -f $(TAG)
@echo "Tagged as $(TAG)"
.PHONY: release
release: tag archive
.PHONY: archive
archive: po-pull ChangeLog
$(PYTHON) setup.py $(BUILD_ARGS)
@echo "The archive is in dist/$(PKGNAME)-$(VERSION).tar.gz"
.PHONY: rpmlog
rpmlog:
@git log --no-merges --pretty="format:- %s (%ae)" $(TAG).. |sed -e 's/@.*)/)/'
@echo
.PHONY: potfile
potfile:
$(MAKE) -C po potfile
.PHONY: po-pull
po-pull:
TEMP_DIR=$$(mktemp --tmpdir -d $(SPECNAME)-localization-XXXXXXXXXX) && \
git clone --depth 1 -b $(GIT_L10N_BRANCH) -- $(L10N_REPOSITORY) $$TEMP_DIR && \
cp $$TEMP_DIR/$(L10N_DIR)/*.po ./po/ && \
rm -rf $$TEMP_DIR
.PHONY: po-push
po-push: potfile
# This algorithm will make these steps:
# - clone localization repository
# - copy pot file to this repository
# - check if pot file is changed (ignore the POT-Creation-Date otherwise it's always changed)
# - if not changed:
# - remove cloned repository
# - if changed:
# - add pot file
# - commit pot file
# - tell user to verify this file and push to the remote from the temp dir
TEMP_DIR=$$(mktemp --tmpdir -d $(SPECNAME)-localization-XXXXXXXXXX) || exit 1 ; \
git clone --depth 1 -b $(GIT_L10N_BRANCH) -- $(L10N_REPOSITORY_RW) $$TEMP_DIR || exit 2 ; \
cp ./po/$(SPECNAME).pot $$TEMP_DIR/$(L10N_DIR)/ || exit 3 ; \
pushd $$TEMP_DIR/$(L10N_DIR) ; \
git difftool --trust-exit-code -y -x "diff -u -I '^\"POT-Creation-Date: .*$$'" HEAD ./$(SPECNAME).pot &>/dev/null ; \
if [ $$? -eq 0 ] ; then \
popd ; \
echo "Pot file is up to date" ; \
rm -rf $$TEMP_DIR ; \
else \
git add ./$(SPECNAME).pot && \
git commit -m "Update $(SPECNAME).pot" && \
popd && \
echo "Pot file updated for the localization repository $(L10N_REPOSITORY)" && \
echo "Please confirm changes and push:" && \
echo "$$TEMP_DIR" ; \
fi ;
.PHONY: bumpver
bumpver: po-push
read -p "Please see the above message. Verify and push localization commit. Press anything to continue." -n 1 -r
@NEWSUBVER=$$((`echo $(VERSION) |cut -d . -f 2` + 1)) ; \
NEWVERSION=`echo $(VERSION).$$NEWSUBVER |cut -d . -f 1,3` ; \
DATELINE="* `LC_ALL=C.UTF-8 date "+%a %b %d %Y"` `git config user.name` <`git config user.email`> - $$NEWVERSION-1" ; \
cl=`grep -n %changelog $(SPECNAME).spec |cut -d : -f 1` ; \
tail --lines=+$$(($$cl + 1)) $(SPECNAME).spec > speclog ; \
(head -n $$cl $(SPECNAME).spec ; echo "$$DATELINE" ; make --quiet rpmlog 2>/dev/null ; echo ""; cat speclog) > $(SPECNAME).spec.new ; \
mv $(SPECNAME).spec.new $(SPECNAME).spec ; rm -f speclog ; \
sed -i "s/Version: $(VERSION)/Version: $$NEWVERSION/" $(SPECNAME).spec ; \
sed -i "s/version='$(VERSION)'/version='$$NEWVERSION'/" setup.py
.PHONY: pypi-upload
pypi-upload:
$(PYTHON) -m twine upload dist/*
.PHONY: ci
ci: check test
@@ -0,0 +1,48 @@
[![Copr build status](https://copr.fedorainfracloud.org/coprs/g/rhinstaller/Anaconda/package/python-simpleline/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/g/rhinstaller/Anaconda/package/python-simpleline/)
[![Translation status](https://translate.fedoraproject.org/widgets/python-simpleline/-/master/svg-badge.svg)](https://translate.fedoraproject.org/engage/python-simpleline/?utm_source=widget)
Python Simpleline
=================
This is a text UI framework originally which was a part of the
[Anaconda](https://github.com/rhinstaller/anaconda) installer project.
This UI is simple and easy to use. It is designed to be used with line-based
machines and tools (e.g. serial console) so that every new line is appended
to the bottom of the screen. Printed lines are never rewritten!
How to
======
[Link](https://python-simpleline.readthedocs.io) to the documentation.
Another way to learn how to use this library is to view the
[examples directory](https://github.com/rhinstaller/python-simpleline/tree/master/examples).
Examples can be run without installing Simpleline to the system. You can test
them by running the script `run_example.sh`.
1. `cd examples`
2. `./run_example.sh example_folder_name`
For example:
`./run_example.sh basic`
Dependencies
============
This is a Python3-only project. This code should not be difficult to migrate to
Python2. However, there is no need from the community, so it is only compatible
with Python3 at the moment.
No special libraries are required to use this library. If you want to use glib
event loop instead of the original one you need to install glib and Python3
gobject introspection.
If you want to run tests (`make ci`), you need to install
[Pocketlint](https://github.com/rhinstaller/pocketlint) and glib with gobject
introspection for Python3.
Note
====
Thanks to Martin Sivák for the original Anaconda project. It was really nice
starting place for the new Simpleline's form.
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build-3
SPHINXPROJ = Simpleline
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,22 @@
.. _public_api_label:
Public API
==========
API listed here is the public API. Developers shouldn't use anything which is not mentioned here!
.. toctree::
:maxdepth: 1
:caption: Contents:
App <api/app>
UIScreen <api/screen>
Screen Handling <api/screen_handling>
Widgets <api/widgets>
Containers <api/containers>
Prompt <api/prompt>
Advanced Widgets <api/adv_widgets>
Event loops <api/event_loops>
Signals <api/signals>
Errors <api/errors>
Advanced Input <api/advanced_input>
@@ -0,0 +1,13 @@
Advanced Widgets
================
Advanced widgets are :class:`screens <simpleline.render.screen.UIScreen>` which can be used for a
specific purpose. For example, reading input from the user and testing acceptance conditions on this
input, or asking the user a yes/no question.
Advanced widget classes
-----------------------
.. automodule:: simpleline.render.adv_widgets
:members:
:show-inheritance:
@@ -0,0 +1,92 @@
Advanced Input
==============
.. automodule:: simpleline.input.input_handler
.. WARNING::
This section contains advanced input techniques which may lead to buggy code in your
program if they are not used correctly.
The default technique to get user input is already described in :ref:`UIScreen <uiscreen_label>`,
and that should be the preferred way to obtain user input. However, if the default technique is
not enough for your situation then read the text below to find out how to implement custom input.
Input handler classes
---------------------
Input handler classes are classes created to obtain user input. If required
:class:`InputHandler` can block an application until user input is received.
This class can be instantiated everywhere in the code and used to ask for user input::
handler = InputHandler()
handler.get_input("Shut up and give me your input:")
handler.wait_for_input()
if handler.input_successful:
user_input = handler.value
The :meth:`InputHandler.wait_on_input` method will block code processing until user input is
received. Input should always be checked before processing.
In case some other work needs to be done before user input is received, then pass
a callback to the constructor of the :class:`InputHandler` class and do not use
the :meth:`InputHandler.wait_on_input` method. However, the callback is using
the :ref:`event loop<event_loops_label>` so it won't be called until event loop
processing is active. If :ref:`concurrent input<concurrent_input_label>` is used then this
callback might never get called!
If what a user types must not be displayed, then the :class:`PasswordInputHandler` class
should be used. It shares most of its implementation with the :class:`InputHandler` but overrides
how to obtain the code. For more info look at the :class:`PasswordInputHandler` class
documentation.
.. _concurrent_input_label:
Concurrent input
----------------
Concurrent input is something which should be avoided. It drags unexpected behavior into an
application and is hard to debug. However, there could be an instance when user input is required
immediately, even when an application is already waiting for other input.
By default, every attempt for concurrent input will raise an exception and kill the application to
prevent unexpected behavior. In order to allow for concurrent input, the
:attr:`InputHandler.skip_concurrency_check` property must be set. After this property is disabled
for the :class:`InputHandler` instance, then the handler instance then it can support
concurrent input.
The last registered concurrent input will result in dropping all other waiting inputs -- even other
waiting inputs with :attr:`InputHandler.skip_concurrency_check` will be dropped. The dropped
waiting inputs will get a failed input signal to unblock :meth:`InputHandler.wait_on_input`
methods.
Creating a custom InputHandler
------------------------------
If the :class:`InputHandler` class or the :class:`PasswordInputHandler` class is not enough,
developers can create their own handler. The structure of an input handler is based on two
classes. First is a handler itself, and second is the request object it creates.
The handler class is used as an interface for the rest of the application. It also creates
the requester instance. The requester object is a low-level implementation of obtaining user input.
The requester object has to have the ``get_input`` method and should contain the ``text_prompt``
method. The ``get_input`` method is called in a separate thread and should prompt the user for
input. The ``text_prompt`` method is required mainly for concurrent input, and it returns
a string representation of the prompt.
For more details, please look at the implementation of the :class:`InputHandler` class.
InputHandler class
------------------
.. autoclass:: InputHandler
:members:
PasswordInputHandler class
--------------------------
.. autoclass:: PasswordInputHandler
:members:
:inherited-members:
@@ -0,0 +1,48 @@
App
===
.. automodule:: simpleline
The App class is the heart of Simpleline. It holds the :ref:`event loop <event_loops_label>`
instance and scheduler which are used by Simpleline. Both can be replaced if needed in the
initialization phase by calling the :meth:`App.initialize` method. However, by replacing the
scheduler you are replacing most of the logic in Simpleline. Replacing the scheduler is not
supported, since it is currently not a part of the public API.
All Simpleline applications must be run by the :meth:`App.run` method. This method will start
the event loop instance created in the initialization process.
If a reaction on application quit is required, please set
:meth:`set_quit_callback <event_loop.AbstractEventLoop.set_quit_callback>` in the used event loop.
This can be done by::
loop = App.get_event_loop()
loop.set_quit_callback(callback_function)
**Do not instantiate the** :class:`App` **class!** It is designed to be used in a purely static way.
Application configuration
-------------------------
Configuration of the application is saved in the
:class:`GlobalConfiguration <global_configuration.GlobalConfiguration>` class. This class can be
created before :meth:`App.initialize` is called and passed in as a
parameter. This way the same configuration can be used between re-initialization of the
application. The configuration can easily be changed, even while an application is running,
by setting desired properties.
Look at the :class:`GlobalConfiguration <global_configuration.GlobalConfiguration>` to find out
all the configuration possibilities.
App class
---------
.. autoclass:: App
:members:
GlobalConfiguration class
-------------------------
.. autoclass:: simpleline.global_configuration.GlobalConfiguration
:members:
@@ -0,0 +1,47 @@
.. _containers_label:
Containers
==========
Containers are structures to hold widgets and handle automatic positioning. Containers are
essentially widgets containing other widgets. Below is a collection of default containers which
can position :ref:`widgets <widgets_label>`, but they can do more (e.g. handle user input).
Recursive composition of containers is supported.
Customized containers can also be created. See the :ref:`creating_a_custom_container_label`
section.
Container classes
-----------------
.. automodule:: simpleline.render.containers
:members:
:inherited-members:
:show-inheritance:
.. _creating_a_custom_container_label:
Creating a custom container
---------------------------
If an existing container is missing a required feature, a new, customized container can be
created based on the :class:`Container` class. Container creation is essentially the same as
:ref:`custom widget creation <create_custom_widget_label>` because containers are based on widgets.
The main difference is that containers are working with widgets added by a developer when using
the container. As an example, every :class:`UIScreen <simpleline.render.screen.UIScreen>` has a
:class:`WindowContainer`, and this is used as the main rendering point.
To create a customized container, the :meth:`Container.render` method should be overridden and
the positioning of widgets and containers should be done here. It can even enhance these widgets,
for example, by adding numbering (this is done in :class:`ListRowContainer` or
:class:`ListColumnContainer`). The :meth:`Container.render` method should call the
:meth:`Container.draw` method. The :meth:`Container.draw` method should be called for every
widget placed. For a better understanding please refer to the existing implementation.
Base container class
--------------------
.. autoclass:: Container
:members:
:inherited-members:
:show-inheritance:
@@ -0,0 +1,39 @@
Errors module
=============
.. automodule:: simpleline.errors
Collection of generic exception classes used everywhere in the project. The most important one is
:class:`SimplelineError`, which is the base exception for all the other exceptions used in
the Simpleline project.
.. autoexception:: SimplelineError
:members:
:show-inheritance:
Render exceptions
-----------------
Exceptions used for rendering errors.
.. automodule:: simpleline.render
.. autoexception:: RenderError
:members:
:show-inheritance:
.. autoexception:: RenderUnexpectedError
:members:
:show-inheritance:
Event loop exceptions
---------------------
Exceptions used for errors in event loops.
.. automodule:: simpleline.event_loop
.. autoexception:: simpleline.event_loop.ExitMainLoop
:members:
:show-inheritance:
@@ -0,0 +1,92 @@
.. _event_loops_label:
Event Loops
===========
.. currentmodule:: simpleline.event_loop
Event loops are the heart of Simpleline. Every event loop is based on the
:class:`AbstractEventLoop`, and they all work with :ref:`signals <signals_label>`.
A signal is a message passed to the loop containing some information. Signals are passed to
an event loop by calling :meth:`AbstractEventLoop.enqueue_signal`. These signals are then
processed by calling :meth:`AbstractEventLoop.process_signals`. This method can be called by
an application developer manually or by the :meth:`AbstractEventLoop.run` method, which is
called by :meth:`App.run() <simpleline.App.run>` to start the Simpleline-based application.
When a signal is processed, all handlers attached to this signal are called. Signal handler
assignment is done by the :meth:`AbstractEventLoop.register_signal_handler` method.
Event loops can also be started recursively by :meth:`AbstractEventLoop.execute_new_loop`.
The old event loop is waiting for this event loop to stop. New loop execution is mandatory for
modal screens to work, since they can't be interrupted by other screens. This new event
loop is terminated by closing the last screen in the event loop or by calling
the :meth:`AbstractEventLoop.close_loop` method.
The last event loop should be terminated by closing the last screen in the screen stack or by
calling :meth:`AbstractEventLoop.close_loop`. In case a fatal error occurs the
:meth:`AbstractEventLoop.force_quit` method can be used to immediately kill the loop.
If a reaction on quitting the application (closing the last event loop) is required, the quit
callback can be used. The quit callback can be set by
the :meth:`AbstractEventLoop.set_quit_callback` method.
The following event loops are supported by Simpleline, but you can also
:ref:`Create_your_own_loop_label` :
* :ref:`MainLoop_label`
* :ref:`GLib_Event_loop_label`
.. _MainLoop_label:
Main Loop
---------
The main loop is the default event loop for Simpleline projects. The benefit of using
:class:`MainLoop <main_loop.MainLoop>` is that it isn't necessary to have any dependencies on
other libraries. It is a lightweight event loop implemented completely in Python.
.. autoclass:: simpleline.event_loop.main_loop.MainLoop
:members:
:inherited-members:
:show-inheritance:
.. _GLib_Event_loop_label:
GLib Event loop
---------------
The GLib event loop was added in order to utilize existing event loops used by other libraries,
for example, DBus connections. Simpleline with this loop should have the same behavior as with
the :ref:`MainLoop_label`.
To use this loop you need to set it via the :class:`App <simpleline.App>` class::
# Create Glib event loop.
glib_loop = GLibEventLoop()
# Use glib event loop instead of the original one.
# Everything else should behave the same as with the original Simpleline loop.
App.initialize(event_loop=glib_loop)
The GLib loop can be accessed by the
:attr:`GLibEventLoop.active_main_loop <simpleline.event_loop.glib_event_loop.GLibEventLoop.active_main_loop>`
property, or by getting the default loop from GLib directly
`GLib.MainLoop() <https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html>`_.
.. autoclass:: simpleline.event_loop.glib_event_loop.GLibEventLoop
:members:
:inherited-members:
:show-inheritance:
.. _Create_your_own_loop_label:
Create your own loop
--------------------
If new loop support is required, it should inherit from :class:`AbstractEventLoop` and
implement the same behavior as the :ref:`MainLoop_label`. You can use existing tests from the
event loops to start. If the new loop is stable enough, pull requests are always welcome at
`Simpleline repository <https://github.com/rhinstaller/python-simpleline>`_.
.. autoclass:: simpleline.event_loop.AbstractEventLoop
:members:
:inherited-members:
@@ -0,0 +1,23 @@
.. _prompt_label:
Prompt
======
.. automodule:: simpleline.render.prompt
Class for prompting a user for input. New user options can be added by :meth:`Prompt.add_option`,
removed by :meth:`Prompt.remove_option` or updated by :meth:`Prompt.update_option`.
A message for the user can also be set by the :meth:`Prompt.set_message` method.
This class is used in the :class:`UIScreen <simpleline.render.screen.UIScreen>` class. The default
instance always handles *r* (refresh), *c* (continue) and *q* (quit) and is created in the
:meth:`UIScreen.prompt() <simpleline.render.screen.UIScreen.prompt>` method. To create your own
custom prompt please override
the :meth:`UIScreen.prompt() <simpleline.render.screen.UIScreen.prompt>` method.
Prompt class
------------
.. autoclass:: Prompt
:members:
:inherited-members:
@@ -0,0 +1,126 @@
.. _uiscreen_label:
UIScreen
========
.. automodule:: simpleline.render.screen
The base class for creating a new screen. :class:`UIScreen` is used for any user interaction.
:class:`UIScreen` uses :ref:`containers <containers_label>` and :ref:`widgets <widgets_label>`
to present information to a user, and the :meth:`UIScreen.input` method to get input from a user
to an application. Screens are pushed to the screen stack, which are then used to
communicate with a user. See the :ref:`screen handling <screen_handling_label>` section to find
out more.
The methods :meth:`UIScreen.redraw`, :meth:`UIScreen.close`, :meth:`UIScreen.emit` and
:meth:`UIScreen.create_and_emit` are asynchronous. These methods will create a signal which
is passed to the event loop for later processing.
**Beware**, methods :meth:`UIScreen.redraw` and :meth:`UIScreen.close` can lead to unexpected
behavior when multiple instances of these signals are emitted (methods are called multiple times).
This can even crash your application.
Lifecycle
---------
Every :class:`UIScreen` has a distinct lifecycle: *uninitialized*, *initialized*, *draw*,
*process input* and *closed*. If the screen has already been initialized
(it was *drawn* to a monitor) and will be shown again, then the screen skips
the *uninitialized* stage. The screen can cycle between *draw* and *process input* stages by
calling the :meth:`UIScreen.redraw` method after processing user input.
In case :meth:`UIScreen.redraw` won't be called and no new screen is pushed, or this screen
wasn't closed, then an application will stay in an infinite loop waiting for something to happen.
This is correct behavior because there could be something in an event loop which will
call :meth:`UIScreen.redraw` later.
Rendering widgets
-----------------
The :meth:`UIScreen.refresh` method is the most important part of the :class:`UIScreen`.
It contains preparations for rendering (creating widgets and adding them to containers).
The :meth:`UIScreen.refresh` method will be called before anything is drawn on a monitor.
The :attr:`UIScreen.window` attribute, which is the
:class:`WindowContainer <simpleline.render.containers.WindowContainer>` instance, contains
all items (widgets, containers) which are to be rendered by the screen.
A new :class:`WindowContainer <simpleline.render.containers.WindowContainer>` is created in the
:meth:`UIScreen.refresh` method for every screen redraw. The :attr:`UIScreen.title` attribute is
passed to the :attr:`WindowContainer.title <simpleline.render.containers.WindowContainer.title>`
property, and a developer can add :ref:`widgets <widgets_label>` and other
:ref:`containers <containers_label>` to present items to a user by calling
:meth:`WindowContainer.add() <simpleline.render.containers.WindowContainer.add>` or
:meth:`WindowContainer.add_with_separator() <simpleline.render.containers.WindowContainer.add_with_separator>`.
Multiple items can be added by calling these methods repeatedly.
When everything is prepared properly in the :meth:`UIScreen.refresh` method, it needs to be drawn
on the monitor for a user. This is handled by the :meth:`UIScreen.show_all` method. This method
works automatically, but it could also be useful for a developer, especially when the screen will
not process input. In this case, the :meth:`UIScreen.input` method is not called at all.
Developers can override this method and call the parent class's :meth:`UIScreen.show_all`,
which will handle drawing the screen and any additional processing.
Redrawing the screen can be invoked by the :meth:`UIScreen.redraw` method. However, this is
not processed immediately. Instead it will be added to the event loop and processed later, when the
loop is idle. Beware, after every :meth:`redraw <UIScreen.redraw>` call the input is processed
if not disabled, so if multiple redraw signals are emitted, than the application will
crash.
The :meth:`UIScreen.redraw` method is also invoked when a screen is
:ref:`pushed <push_screen_label>` to the stack.
User input processing
---------------------
If the screen shouldn't process user input, the :attr:`UIScreen.input_required` property needs
to be set to `False`. `True` is the default value for this property.
After everything is printed to a monitor, the :class:`UIScreen` will wait for user input.
For this purpose there is :meth:`UIScreen.input`, which is called when a user passes string input
to a screen. The screen needs to react upon user input and return one of the options from the
:class:`InputState` enum or the user input string.
To accept the user input, :attr:`InputState.PROCESSED`, :attr:`InputState.PROCESSED_AND_REDRAW` or
:attr:`InputState.PROCESSED_AND_CLOSE` should be returned. Addition to accepting user input the
:attr:`InputState.PROCESSED_AND_REDRAW` value will also redraw active screen and
:attr:`InputState.PROCESSED_AND_CLOSE` will close active screen. However, if
:attr:`InputState.PROCESSED` is used then the developer is responsible for not ending in frozen
application. The :meth:`UIScreen.refresh` or the :meth:`UIScreen.close` methods must be called
manually.
In case the user input is invalid, the :attr:`InputState.DISCARDED` value should be returned.
This will reject the user input and wait for another attempt. The :class:`UIScreen.refresh`
method will be called after 5 rejections and show the screen output again.
If the user input string is returned it will be checked for
options of the :class:`Prompt <simpleline.render.prompt.Prompt>` instance which can either
close the screen, refresh the screen (this will call :meth:`UIScreen.refresh`)
or quit the application.
Closing screens
---------------
There are several ways to close a screen. One is by calling :meth:`UIScreen.close` or
by pressing *c* to continue (with the default
:class:`Prompt <simpleline.render.prompt.Prompt>` class). When the screen is closed
the next screen on the stack will be shown. A screen can also be
:ref:`replaced <replace_screen_label>`. Then the original screen is removed from the stack
without closing a screen. If a reaction on closing a screen is required then the
:meth:`UIScreen.closed` callback should be overridden.
**Beware**, when calling :meth:`UIScreen.close` multiple times it will close multiple screens.
This is because the close signal will always close the top screen on the screen stack.
UIScreen class
--------------
.. autoclass:: UIScreen
:members:
:inherited-members:
InputState enum
---------------
.. autoclass:: InputState
:members:
:undoc-members:
@@ -0,0 +1,82 @@
.. _screen_handling_label:
Screen Handling
===============
.. automodule:: simpleline.render.screen_handler
The :class:`ScreenHandler` class is used to schedule a
:class:`UIScreen <simpleline.render.screen.UIScreen>` to the screen stack.
Screen handling is an important part of using the Simpleline library, and it is recommended
for a developer to get familiar with this principle. Screen handling is the gateway by which
a developer manages the stack, by either adding or removing screens from it.
There are many ways to add a screen to the stack. To remove a screen from the stack, the screen
must be closed, or it can be replaced by another screen.
To close a :class:`UIScreen <simpleline.render.screen.UIScreen>` a developer should call
the :meth:`UIScreen.close() <simpleline.render.screen.UIScreen.close()>`
method. A screen can also be closed when a user presses `c` (which can be disabled). This will
close the screen automatically. When a screen is closed, the next screen on the top of
the stack will be rendered. If the stack is empty then the application will close.
**Do not instantiate the** :class:`ScreenHandler` **class!** All methods in the
:class:`ScreenHandler` class are class methods so the :class:`ScreenHandler` shouldn't be
instantiated at all.
The following operations can be used to schedule a screen.
Schedule screen
---------------
To schedule a screen use the :meth:`ScreenHandler.schedule_screen` method.
Scheduling a screen should be used on the first screen in your application before starting the
event loop. The screen is added to the bottom of the screen stack, and it will be visible as
the last screen in a stack.
This is the only way to add a screen to the screen stack without emitting a redraw call.
.. _push_screen_label:
Push screen
-----------
To push a screen to the stack use the :meth:`ScreenHandler.push_screen` method.
Pushing a screen to the stack will place a screen on top of the stack, so it will be
drawn on the next redraw call. The original screen will remain on the stack, so after the new
screen is closed, the original screen will be rendered again. If you need to avoid this behavior
please close the active screen before pushing a new screen to the stack.
The redraw signal will be emitted automatically when a screen is pushed.
.. _push_screen_modal_label:
Push screen as modal
--------------------
To push a modal screen to the stack use the :meth:`ScreenHandler.push_screen_modal` method.
This behaves the same as :ref:`push_screen_label` with one important difference. The pushed screen
will act as a modal screen. A modal screen has its own event loop, so event processing of the old
loop is blocked until the modal screen is closed. The code processing is also blocked by the
:meth:`ScreenHandler.push_screen_modal` method call.
The :meth:`UIScreen.redraw() <simpleline.render.screen.UIScreen.redraw>` method may be
required if the original screen was already drawn before invoking the
:meth:`ScreenHandler.push_screen_method` method.
The redraw signal will be emitted automatically when a screen is pushed.
.. _replace_screen_label:
Replace screen
--------------
Replace an existing screen with a new screen. This behaves like :ref:`push_screen_label` but it
replaces the original screen, so there is no need to close the original screen.
The redraw signal will be emitted automatically when a screen is replaced.
ScreenHandler class
-------------------
.. autoclass:: ScreenHandler
:members:
@@ -0,0 +1,22 @@
.. _signals_label:
Signals
=======
This is a collection of signals that can be used in the
:class:`event loop <simpleline.event_loop.AbstractEventLoop>`.
.. automodule:: simpleline.event_loop.signals
:members:
:inherited-members:
:show-inheritance:
Creating custom signals
-----------------------
New signals can be created by subclassing an existing signal class or
:class:`AbstractSignal <simpleline.event_loop.AbstractSignal>`
.. autoclass:: simpleline.event_loop.AbstractSignal
:members:
:inherited-members:
@@ -0,0 +1,44 @@
.. _widgets_label:
Widgets
=======
.. currentmodule:: simpleline.render.widgets
Widgets are the basic units to render items on a
:class:`screen <simpleline.render.screen.UIScreen>`. Widgets can wrap a common text
(:class:`TextWidget`) or create empty lines (:class:`SeparatorWidget`). They can also be more
complex structures (:class:`CheckboxWidget`). A new widget can also be created.
See :ref:`create_custom_widget_label`.
These widgets should be used in :ref:`containers_label`.
Widgets classes
---------------
.. automodule:: simpleline.render.widgets
:members:
:inherited-members:
:show-inheritance:
.. _create_custom_widget_label:
Creating a custom widget
------------------------
To create a custom widget you should subclass the :class:`Widget` class. The most important method
for creating a customized widget is :meth:`Widget.render`. This method is responsible for
presenting a user textual information.
If the new widget is composed of other widgets the :meth:`Widget.draw` method should be
called on every widget used. The :meth:`Widget.write` method should be called if the input
is a string. These calls can be repeated multiple times. For an example please look at the
existing implementation.
Base Widget class
-----------------
.. autoclass:: Widget
:members:
:inherited-members:
:show-inheritance:
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Simpleline documentation build configuration file, created by
# sphinx-quickstart on Fri Nov 3 11:00:41 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.imgmath']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Simpleline'
copyright = '2017, Jiri Konecny' # pylint: disable=redefined-builtin
author = 'Jiri Konecny'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
def get_version():
"""Read version from ../python-simpleline.spec ."""
import re
version_re = re.compile(r"^Version: *([\d.]+)$")
with open("../python-simpleline.spec", "r") as f:
for line in f:
m = version_re.match(line)
if m:
return m.group(1)
# The short X.Y version.
version = get_version()
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Simplelinedoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Simpleline.tex', 'Simpleline Documentation',
'Jiri Konecny', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'simpleline', 'Simpleline Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Simpleline', 'Simpleline Documentation',
author, 'Simpleline', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3': None}
# -- Mock missing stuff in readthedocs ------------------------------------
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return MagicMock()
MOCK_MODULES = ['gi', 'gi.repository']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
@@ -0,0 +1,176 @@
.. _guide_label:
Guide to Simpleline
===================
Simpleline is a text user interface framework written completely in Python 3 with a possibility to
have :ref:`non-python event loops <event_loops_label>`. With the exception of optional event loops,
Simpleline has almost no dependency on external libraries.
This UI is simple and easy to use. It is designed to be used with line-based machines and tools
(e.g. serial console) so that every new line is appended to the bottom of the screen.
Printed lines are never rewritten!
Basic components
----------------
For every application, the following parts are always required.
* The :class:`App <simpleline.App>` static class to initialize and run application.
* The :class:`ScreenHandler <simpleline.render.screen_handler.ScreenHandler>` static class for
scheduling screens.
* The :class:`UIScreen <simpleline.render.screen.UIScreen>` based classes to create screens which
will form the application.
* :ref:`Widgets <widgets_label>` to show anything on the screens.
* :ref:`Containers <containers_label>` to position widgets on the screen.
Look at the next section to see how everything fits together.
How to create a simple application
----------------------------------
.. currentmodule:: simpleline.render.screen
Interaction with a user is necessary to have a useful UI framework. To show anything to a user the
:class:`UIScreen` class must be used. So we will subclass this class to create our screen and set
a title for it::
class DividerScreen(UIScreen):
def __init__(self):
# Set title of the screen.
super().__init__(title=u"Divider")
self._message = 0
The ``self._message`` variable will be used later to show results to the user.
The screen's main purpose is to present content to a user. For this we need widgets and containers.
The :attr:`UIScreen.window` attribute is the most important part of the screen for rendering.
It contains the :class:`WindowContainer <simpleline.render.containers.WindowContainer>`
container which is created and filled up by the :meth:`UIScreen.refresh` method.
Everything added to this container is printed to the monitor.
We should override the :meth:`UIScreen.refresh` method and call the parent's version to
prepare the container. Then we can add :ref:`widgets <widgets_label>` to the container.
The screen will continue like this:
.. code-block:: python
:dedent: 0
def refresh(self, args=None):
# Fill the self.window attribute by the WindowContainer and set screen title as header.
super().refresh()
widget = TextWidget("Result: " + str(self._message))
self.window.add_with_separator(widget)
The :meth:`WindowContainer.add_with_separator() <simpleline.render.containers.WindowContainer.add_with_separator>`
method will print a blank line after the
:class:`TextWidget's <simpleline.render.widgets.TextWidget>` text.
Now the user has the header and result printed on the screen but it would be nice to give them a
hint about how to use the Divider screen. The best part of the screen for this is the
:class:`Prompt <simpleline.render.prompt.Prompt>`.
The :class:`Prompt <simpleline.render.prompt.Prompt>` class is responsible for guiding the user
by giving them a set of possible options to choose from.
We will set the message of the prompt inside of the :meth:`UIScreen.prompt` method. We also
remove the default option to continue, because it functions the same as quitting when there is
only one screen in the application.
.. code-block:: python
:dedent: 0
def prompt(self, args=None):
# Change user prompt
prompt = super().prompt()
# Set message to the user prompt. Give a user hint how he/she may control our application.
prompt.set_message("Pass numbers to divider in a format: 'num / num'")
# Remove continue option from the control. There is no need for that
# when we have only one screen.
prompt.remove_option('c')
return prompt
When we are able to present our content to a user, we want to have the possibility to process
user input. For this purpose there is the :meth:`UIScreen.input` method. Input from a user is
passed to this method, and the screen may process it, discard it or return it for further
processing.
If input processing is not required and the screen should only be used for displaying
information to a user, then the :attr:`UIScreen.input_required` property should be set to `False`.
However, in this case you need to :meth:`close <UIScreen.close>`, :meth:`redraw <UIScreen.redraw>`
or :ref:`push <screen_handling_label>` a new screen manually. This can be done, for example, in the
:meth:`UIScreen.show_all` method.
.. code-block:: python
:dedent: 0
def input(self, args, key):
"""Process input from user and catch numbers with '/' symbol."""
# Test if user passed valid input for divider.
# This will basically take number + number and nothing else and only positive numbers.
groups = re.match(r'(\d+) *\/ *(\d+)$', key)
if groups:
num1 = int(groups[1])
num2 = int(groups[2])
# Dividing by zero is not valid so we won't accept this input from the user. New
# input is then required from the user.
if num2 == 0:
return InputState.DISCARDED
self._message = int(num1 / num2)
# Because this input is processed we need to show this screen (show the result)
# again by returning PROCESSED_AND_REDRAW.
# This will call the refresh method so our new result will be processed inside
# of the refresh() method.
return InputState.PROCESSED_AND_REDRAW
else:
# Not input for our screen, try other default inputs. This will result in the
# same state as DISCARDED when no default option is used.
return key
.. py:currentmodule:: simpleline
Our screen is finished. Next, we need to use it in our application. To run an application the
:class:`App` static class must be used.
This class will initialize an event loop and the scheduler by the :meth:`App.initialize` method.
When the application is initialized, we need to pass our screen to the screen stack (you can
pass multiple screens but we have only one here). To pass a screen to the screen stack we will use
:class:`ScreenHandler <simpleline.render.screen_handler.ScreenHandler>`. For further explanation
on screen scheduling, refer to the :ref:`screen_handling_label` section.
.. code-block:: python
:dedent: 0
if __name__ == "__main__":
# Initialize application (create scheduler and event loop).
App.initialize()
# Create our screen.
screen = DividerScreen()
# Schedule screen to the screen scheduler.
# This can be called only after App.initialize().
ScreenHandler.schedule_screen(screen)
# Run the application. You must have some screen scheduled
# otherwise it will end in an infinite loop.
App.run()
Well done! You have your first application in Simpleline.
Further reading
---------------
I would recommend everyone who wants to use Simpleline to look at the
`examples <https://github.com/rhinstaller/python-simpleline/tree/master/examples>`_ which is one
of the best sources of information. Another place to look is the :ref:`public_api_label`
documentation section.
@@ -0,0 +1,23 @@
.. Simpleline documentation master file, created by
sphinx-quickstart on Fri Nov 3 11:00:41 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Simpleline's documentation!
======================================
.. toctree::
:maxdepth: 1
:caption: Contents:
introduction
guide
api
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
@@ -0,0 +1,102 @@
Introduction
============
Simpleline is a text UI framework. Originally a part of the Anaconda installer project.
It is designed to be used with line-based machines and tools (e.g. serial console) so that
every new line it appended to the bottom of the screen. Printed lines are never rewritten!
How to use
----------
The best learning sources can be found in the
`examples directory <https://github.com/rhinstaller/python-simpleline/tree/master/examples>`_ in
the `GitHub repository <https://github.com/rhinstaller/python-simpleline>`_ and you can read the
:ref:`guide_label` section of this documentation. However, some basic usage of Simpleline will be
shown here too, to get an idea of how Simpleline works::
from simpleline import App
from simpleline.render.screen import UIScreen
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget
# UIScreen is the main building item for Simpleline. Every screen
# which will user see should be inherited from UIScreen.
class HelloWorld(UIScreen):
def __init__(self):
# Set title of the screen.
super().__init__(title=u"Hello World")
def refresh(self, args=None):
# Fill the self.window attribute by the WindowContainer and set screen title as header.
super().refresh()
widget = TextWidget("Body text")
self.window.add_with_separator(widget)
if __name__ == "__main__":
# Initialize application (create scheduler and event loop).
App.initialize()
# Create our screen.
screen = HelloWorld()
# Schedule screen to the screen scheduler.
# This can be called only after App.initialize().
ScreenHandler.schedule_screen(screen)
# Run the application. You must have some screen scheduled
# otherwise it will end in an infinite loop.
App.run()
The output from the simple *Hello World* example above::
$ ./run_example.sh 00_basic
================================================================================
================================================================================
Hello World
Body text
Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]:
If a user presses **r** and then **enter** to refresh, the same screen is printed again.
This will be printed to a monitor::
$ ./run_example.sh 00_basic
================================================================================
================================================================================
Hello World
Body text
Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]: r
================================================================================
================================================================================
Hello World
Body text
Please make a selection from the above ['c' to continue, 'q' to quit, 'r' to
refresh]:
As you can see the whole screen is not rewritten -- only printed again on the bottom. This
is the expected behavior so the actual screen is always at the bottom but you can see the whole
history. This behavior makes working with line based machines and tools much easier.
Dependencies
------------
This is a Python3-only project. This code should not be difficult to migrate to Python2. However,
there is no need from the community, so it is only compatible with Python3 at the moment. No special
libraries are required to use this library. If you want to use glib event loop instead of the
original one you need to install glib and Python3 gobject introspection.
If you want to run tests (make ci), you need to install
`Pocketlint <https://github.com/rhinstaller/pocketlint>`_ and
`glib <https://developer.gnome.org/glib/>`_ with gobject introspection for
`Python3 <https://docs.python.org/3/index.html>`_.
@@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=Simpleline
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
@@ -0,0 +1,60 @@
#!/bin/python3
#
# 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/>.
#
# Basic usage of Simpleline.
#
# Only show screen with header and text in it.
#
from simpleline import App
from simpleline.render.screen import UIScreen
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget
# UIScreen is the main building item for Simpleline. Every screen
# which will user see should be inherited from UIScreen.
class HelloWorld(UIScreen):
def __init__(self):
# Set title of the screen.
super().__init__(title=u"Hello World")
def refresh(self, args=None):
# Fill the self.window attribute by the WindowContainer and set screen title as header.
super().refresh()
widget = TextWidget("Body text")
self.window.add_with_separator(widget)
if __name__ == "__main__":
# Initialize application (create scheduler and event loop).
App.initialize()
# Create our screen.
screen = HelloWorld()
# Schedule screen to the screen scheduler.
# This can be called only after App.initialize().
ScreenHandler.schedule_screen(screen)
# Run the application. You must have some screen scheduled
# otherwise it will end in an infinite loop.
App.run()
@@ -0,0 +1,75 @@
#!/bin/python3
#
# 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/>.
#
# Usage of base widgets.
#
# Show base widgets for user interaction in one screen.
#
from simpleline import App
from simpleline.render.screen import UIScreen
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget, CenterWidget, CheckboxWidget
class HelloWorld(UIScreen):
def __init__(self):
# Set title of the screen.
super().__init__(title=u"Show Widgets")
def refresh(self, args=None):
super().refresh()
# Text widget
# Show text to user. This is basic widget which will handle
# wrapping of words for you.
text_widget = TextWidget("Text widget")
self.window.add_with_separator(text_widget)
# Center widget
# Wrap extisting widget and center it to the middle of the screen.
text = TextWidget("Center widget")
center_widget = CenterWidget(text)
self.window.add_with_separator(center_widget, blank_lines=3) # Add two more blank lines
# Checkbox widget
# Checkbox which can hold 2 states.
checkbox_widget = CheckboxWidget(key="o",
title="Checkbox title",
text="Checkbox text",
completed=True)
self.window.add_with_separator(checkbox_widget)
# Checkbox widget unchecked
checkbox_widget_unchecked = CheckboxWidget(key="o",
title="Checkbox title",
text="Unchecked",
completed=False)
self.window.add_with_separator(checkbox_widget_unchecked)
if __name__ == "__main__":
App.initialize()
screen = HelloWorld()
ScreenHandler.schedule_screen(screen)
App.run()
@@ -0,0 +1,78 @@
#!/bin/python3
#
# 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/>.
#
# How to block the input from a user.
#
# This can be used for example to force a user to set all the required values.
# Application quit callback is also used here.
#
from simpleline import App
from simpleline.render.prompt import Prompt
from simpleline.render.screen import UIScreen, InputState
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget, CenterWidget
def application_quit_cb(args):
"""Call this callback when the application is quitting."""
print("Application is closing. Bye!")
class InfiniteScreen(UIScreen):
def __init__(self):
# We are using title as message here. Any text could be passed to the title.
super().__init__("You need to use 'q' to quit")
self.continue_count = 0
def refresh(self, args=None):
"""Print text to user with number of continue clicked."""
super().refresh(args)
# Print counter to the screen.
widget = TextWidget("You pressed {} times on continue".format(self.continue_count))
# Center this counter to middle of the screen.
center_widget = CenterWidget(widget)
# Add the centered widget to the window container.
self.window.add(center_widget)
def input(self, args, key):
"""Catch 'c' keys for continue and increase counter."""
if key == Prompt.CONTINUE:
self.continue_count += 1
# Do not process 'c' continue anymore.
# This will refresh screen to refresh counter number
return InputState.PROCESSED_AND_REDRAW
# Process other input e.g.: 'r' refresh and 'q' quit.
return key
if __name__ == "__main__":
App.initialize()
screen = InfiniteScreen()
# Get event loop from application.
loop = App.get_event_loop()
# Set quit callback to the loop. When the loop quits this callback will be triggered.
loop.set_quit_callback(application_quit_cb)
ScreenHandler.schedule_screen(screen)
App.run()
@@ -0,0 +1,89 @@
#!/bin/python3
#
# 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 import App
from simpleline.render.screen import UIScreen, InputState
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget, CenterWidget
class Hub(UIScreen):
def __init__(self):
super().__init__("Hub for entry counter")
self._counter_spoke = CounterScreen()
def refresh(self, args=None):
super().refresh(args)
w = CenterWidget(TextWidget("Press '1' to enter Entry counter"))
self.window.add_with_separator(w)
def input(self, args, key):
"""Run spokes based on the user choice."""
if key == "1":
ScreenHandler.push_screen(self._counter_spoke)
# this input was processed
return InputState.PROCESSED
# return for outer processing
# the basic processing is 'c' for continue, 'r' for refresh, 'q' to quit
# otherwise the input is discarded and waiting for a new input
return key
def prompt(self, args=None):
"""Add our information to the prompt."""
prompt = super().prompt(args)
prompt.add_option("1", "to enter counter spoke")
return prompt
class CounterScreen(UIScreen):
def __init__(self):
super().__init__("Counter Screen")
self._counter = 0
def closed(self):
super().closed()
self.screen_ready = False
def setup(self, args=None):
super().setup(args)
self._counter += 1
return True
def refresh(self, args=None):
"""Write message to user."""
super().refresh(args)
w = TextWidget("Counter {}".format(self._counter))
self.window.add_with_separator(CenterWidget(w))
@property
def counter(self):
return self._counter
if __name__ == "__main__":
App.initialize()
hub = Hub()
ScreenHandler.schedule_screen(hub)
App.run()
@@ -0,0 +1,37 @@
#!/bin/python3
#
# 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/>.
#
# Show help screen.
#
# Usage of the HelpScreen advanced widget.
# There are many of advanced widgets which have different uses.
# I've recommend developer to look on them.
from simpleline import App
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.adv_widgets import HelpScreen
if __name__ == "__main__":
App.initialize()
# You need to pass file with help text to the screen.
s = HelpScreen("./04_help/example_help.txt")
ScreenHandler.schedule_screen(s)
App.run()
@@ -0,0 +1,3 @@
This is help to our AWESOME application.
Everything is so simple, therefore no help is needed!
@@ -0,0 +1,202 @@
#!/bin/python3
#
# 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/>.
#
# Hub and spoke implementation.
#
# Advanced example of Simpleline use.
# Hub is the main screen from where you can go to spokes and do work in the spokes
# then you will return to the hub back. You can continue when all the required items
# is set.
# The example of containers use will be showed here.
#
from simpleline import App
from simpleline.render.adv_widgets import PasswordDialog
from simpleline.render.containers import ListRowContainer
from simpleline.render.prompt import Prompt
from simpleline.render.screen import UIScreen, InputState
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget, CenterWidget
class Hub(UIScreen):
def __init__(self):
super().__init__("Hub")
# Container will be used for spokes positioning. Container is always created in
# the refresh() method.
self._container = None
self._create_spokes()
def _create_spokes(self):
"""Create spokes and use their value."""
# Create name spoke
self._name_spoke = SetNameScreen("First name", "John")
# Create surname spoke
self._surname_spoke = SetNameScreen("Surname", "Doe")
# Create the PasswordDialog advanced widget for getting password from a user.
self._password_spoke = PasswordDialog()
def refresh(self, args=None):
"""Refresh method is called always before the screen will be printed.
All items for printing should be updated or created here.
"""
# Init window container. The windows container will be erased here.
# The window container is the base container. Everything for rendering should be put
# into this container, including other containers.
super().refresh(args)
# Add the screen header message before our items.
header = TextWidget("Please complete all the spokes to continue")
header = CenterWidget(header)
self.window.add_with_separator(header, blank_lines=2)
# Create the empty container.
# It will add numbering, process user input and positioning for us.
self._container = ListRowContainer(2)
# Create widget to get user name.
widget = self._create_name_widget()
# Add widget, callback, arguments to the container.
#
# widget - Widget we want to render. It will be numbered automatically.
# Could be container if needed.
# callback - This callback will be called by the ListRowContainer.process_user_input()
# method when a user press the number of this item. Callback will get args
# passed as 3rd argument.
# args - Argument for callback.
self._container.add(widget, self._push_screen_callback, self._name_spoke)
# Create surname widget and add it to the container.
widget = self._create_surname_widget()
self._container.add(widget, self._push_screen_callback, self._surname_spoke)
# Create password widget and add it to the container.
widget = self._create_password_widget()
self._container.add(widget, self._push_screen_callback, self._password_spoke)
# Add the ListRowContainer container to the WindowContainer container.
self.window.add_with_separator(self._container)
def _create_name_widget(self):
"""Create name spoke widget.
Add the actual value below the spoke name.
"""
msg = "First name"
if self._name_spoke.value:
msg += "\n{}".format(self._name_spoke.value)
return TextWidget(msg)
def _create_surname_widget(self):
"""Create surname spoke widget.
Add the actual value below the spoke name.
"""
msg = "Surname"
if self._surname_spoke.value:
msg += "\n{}".format(self._surname_spoke.value)
return TextWidget(msg)
def _create_password_widget(self):
"""Create password spoke widget.
Add the "Password set" text below the spoke name if set.
"""
msg = "Password"
if self._password_spoke.answer:
msg += "\nPassword set."
return TextWidget(msg)
def input(self, args, key):
"""Run spokes based on the user choice."""
# Find out if a user pressed number for an existing widget and call the callback attached
# to it with arguments passed in the refresh() method.
# Return False if the input is not related to the widget.
if self._container.process_user_input(key):
# Do not process other input if spoke is entered.
return InputState.PROCESSED
# Block continue ('c') if everything is not set.
if key == Prompt.CONTINUE:
if self._name_spoke and self._surname_spoke and self._password_spoke.answer:
return key
# catch 'c' key if not everything set
return InputState.DISCARDED
return key
@staticmethod
def _push_screen_callback(target_screen):
"""Push target screen as new screen.
Target screen is passed in as an argument in the refresh() method.
"""
ScreenHandler.push_screen(target_screen)
def prompt(self, args=None):
"""Add information to prompt for user."""
prompt = super().prompt(args)
# Give user hint that he can press 1, 2 or 3 to enter spokes.
prompt.add_option("1,2,3", "to enter spokes")
return prompt
class SetNameScreen(UIScreen):
def __init__(self, message, def_value):
"""Create spoke for setting name and surname.
:param message: Text message as the body for user.
:param def_value: Default value for this spoke.
"""
super().__init__()
self._value = def_value
self._message = message
def refresh(self, args=None):
"""Write message to user."""
super().refresh(args)
w = TextWidget(self._message)
self.window.add(CenterWidget(w))
def prompt(self, args=None):
"""Take user input."""
self._value = self.get_user_input("Write your name: ")
self.close()
@property
def value(self):
"""Return value set by a user in this spoke."""
return self._value
if __name__ == "__main__":
App.initialize()
screen = Hub()
ScreenHandler.schedule_screen(screen)
App.run()
@@ -0,0 +1,62 @@
#!/bin/python3
#
# 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/>.
#
# Use glib event loop instead of the original Simpleline loop.
#
# You need to have the python3-gobject installed.
#
# You can install it on Fedora by running:
#
# dnf install python3-gobject-base
#
#
# This is basic example using Glib event loop.
# You can implement your own loop abstraction. See simpleline/event_loop/glib_event_loop.
#
from simpleline import App
from simpleline.event_loop.glib_event_loop import GLibEventLoop
from simpleline.render.screen import UIScreen
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget
class HelloWorld(UIScreen):
def __init__(self):
super().__init__(title=u"Hello World with GLib")
def refresh(self, args=None):
super().refresh()
self.window.add_with_separator(TextWidget("Body text"))
if __name__ == "__main__":
# Create Glib event loop.
glib_loop = GLibEventLoop()
# Use glib event loop instead of the original one.
# Everything else should behave the same as with the original Simpleline loop.
App.initialize(event_loop=glib_loop)
screen = HelloWorld()
ScreenHandler.schedule_screen(screen)
# Run the application.
App.run()
@@ -0,0 +1,102 @@
#!/bin/python3
#
# 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/>.
#
# Simple divider screen.
#
# User input processing example.
#
#
import re
from simpleline import App
from simpleline.render.screen import UIScreen, InputState
from simpleline.render.screen_handler import ScreenHandler
from simpleline.render.widgets import TextWidget
class DividerScreen(UIScreen):
def __init__(self):
# Set title of the screen.
super().__init__(title=u"Divider")
self._message = 0
def refresh(self, args=None):
# Fill the self.window attribute by the WindowContainer and set screen title as header.
super().refresh()
widget = TextWidget("Result: " + str(self._message))
self.window.add_with_separator(widget)
def prompt(self, args=None):
# Change user prompt
prompt = super().prompt()
# Set message to the user prompt. Give a user hint how he/she may control our application.
prompt.set_message("Pass numbers to divider in a format: 'num / num'")
# Remove continue option from the control. There is no need for that
# when we have only one screen.
prompt.remove_option('c')
return prompt
def input(self, args, key):
"""Process input from user and catch numbers with '/' symbol."""
# Test if user passed valid input for divider.
# This will basically take number + number and nothing else and only positive numbers.
groups = re.match(r'(\d+) *\/ *(\d+)$', key)
if groups:
num1 = int(groups[1])
num2 = int(groups[2])
# Dividing by zero is not valid so we won't accept this input from the user. New
# input is then required from the user.
if num2 == 0:
return InputState.DISCARDED
self._message = int(num1 / num2)
# Because this input is processed we need to show this screen (show the result).
# This will call refresh so our new result will be processed inside of the refresh()
# method.
return InputState.PROCESSED_AND_REDRAW
# Not input for our screen, try other default inputs. This will result in the
# same state as DISCARDED when no default option is used.
return key
if __name__ == "__main__":
# Initialize application (create scheduler and event loop).
App.initialize()
# Create our screen.
screen = DividerScreen()
# Schedule screen to the screen scheduler.
# This can be called only after App.initialize().
ScreenHandler.schedule_screen(screen)
# Run the application. You must have some screen scheduled
# otherwise it will end in an infinite loop.
App.run()
@@ -0,0 +1,51 @@
#!/bin/bash
#
# 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/>.
#
# Script for starting examples from source code.
#
# Author(s): Jiri Konecny <jkonecny@redhat.com>
#
function print_help {
echo "run_example.sh - easy way how to run example without installing module"
echo ""
echo "./run_example.sh [example]"
echo ""
echo "There is one required argument [example] which is name of the test."
echo ""
}
if [[ $# -ne 1 ]]; then
echo "Bad number of arguments" 1>&2
print_help
exit 1
elif [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then
print_help
exit 0
fi
PROJECT_NAME=${1%%/}
pushd $(pwd)
cd $(dirname $0)/
PYTHONPATH="..:." python3 ./$PROJECT_NAME/$PROJECT_NAME.py
popd
@@ -0,0 +1,86 @@
#
# 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/>.
#
# Makefile for the PO files (translation) catalog
#
# $Id$
TOP = ../..
# What is this package?
NLSPACKAGE = python-simpleline
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 ../simpleline/*.py) $(wildcard ../simpleline/**/*.py)
all:: update-po $(MOFILES)
potfile: $(PYSRC)
$(XGETTEXT) -L Python --keyword=_ --keyword=N_ $(PYSRC)
@if cmp -s $(NLSPACKAGE).po $(POTFILE); then \
rm -f $(NLSPACKAGE).po; \
else \
mv -f $(NLSPACKAGE).po $(POTFILE); \
fi; \
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
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,408 @@
%global srcname simpleline
Name: python-%{srcname}
Summary: A Python library for creating text UI
Url: https://github.com/rhinstaller/python-%{srcname}
Version: 1.8
Release: 1%{?dist}
# This tarball was created from upstream git:
# git clone https://github.com/rhinstaller/python-simpleline
# cd python-simpleline && make archive
Source0: https://github.com/rhinstaller/python-%{srcname}/releases/download/%{srcname}-%{version}/%{srcname}-%{version}.tar.gz
License: LGPLv3+
BuildArch: noarch
BuildRequires: make
BuildRequires: python3-devel
BuildRequires: gettext
BuildRequires: python3-setuptools
BuildRequires: intltool
BuildRequires: python3-gobject-base
%description
Simpleline is a Python library for creating text UI.
It is designed to be used with line-based machines
and tools (e.g. serial console) so that every new line
is appended to the bottom of the screen.
Printed lines are never rewritten!
%package -n python3-%{srcname}
Summary: A Python3 library for creating text UI
Requires: rpm-python3
%{?python_provide:%python_provide python3-%{srcname}}
%description -n python3-%{srcname}
Simpleline is a Python3 library for creating text UI.
It is designed to be used with line-based machines
and tools (e.g. serial console) so that every new line
is appended to the bottom of the screen.
Printed lines are never rewritten!
%prep
%setup -q -n %{srcname}-%{version}
%build
%make_build
%install
make DESTDIR=%{buildroot} install
%find_lang python-%{srcname}
%check
make test
%files -n python3-%{srcname} -f python-%{srcname}.lang
%license LICENSE.md
%doc ChangeLog README.md
%{python3_sitelib}/*
%changelog
* Mon Feb 22 2021 Jiri Konecny <jkonecny@redhat.com> - 1.8-1
- Add missing make command to spec file (jkonecny)
- Don't translate the prompt keys (vponcova)
- Enable daily build for Anaconda-devel COPR repository (jkonecny)
- Use Fedora container registry instead of Dockerhub (jkonecny)
- Migrate COPR daily COPR builds to Packit (jkonecny)
- Test build on Fedora ELN (jkonecny)
- Remove packit get-current-action (jkonecny)
- Run tests in GitHub workflow (martin)
- Fix raise-missing-from (W0707) pylint warnings (martin)
- Fix pylint to check test code correctly (jkonecny)
- Use script to run unit tests (jkonecny)
- Use relative imports in tests (jkonecny)
- Change directory structure of unit tests (jkonecny)
- Fix documentation of _process_screen method (jkonecny)
- Fix pylint issues (jkonecny)
- Use pylint instead of pocketlint (jkonecny)
- Add coverage support (jkonecny)
- Make link to exmples directory in Readme (jkonecny)
- Fix homepage of the project in setup.py (jkonecny)
- Fix classifiers in setup.py (jkonecny)
- Add pypi-upload to Makefile (jkonecny)
- Use correct variant of the field (jkonecny)
- Propose Fedora update only to Fedora in development (jkonecny)
- Add upstream tag template to packit for releasing (jkonecny)
- Packit will download archive from Source0 if needed (jkonecny)
* Thu Jun 25 2020 Jiri Konecny <jkonecny@redhat.com> - 1.7-1
- Testing farm seems like a better option (jkonecny)
- Fix upstream package name (jkonecny)
- Copy archive to the project root for packit (jkonecny)
- Use BUILD_ARGS for packit to remove wheel dependency (jkonecny)
- Correct how packit sniffing package version (jkonecny)
- Add packit action to create archive correctly (jkonecny)
- Simplify Packit configuration file (jkonecny)
- Enable copr builds and add packit config (dhodovsk)
- Add possibility to change setup.py arguments (jkonecny)
- Fix typo in setup.py (jkonecny)
- Add translation badge from Weblate (jkonecny)
- Add potfile to the .gitignore (jkonecny)
- Pause releasing when calling bumpver (jkonecny)
- Remove unused PREFIX from the Makefile (jkonecny)
- Remove Zanata from the Simpleline (jkonecny)
- Switch po-push from Zanata to Weblate (jkonecny)
- Download translations from Weblate (jkonecny)
- Remove pot file from this repository (jkonecny)
- Add new directories to .gitignore and make clean (jkonecny)
- Add Makefile PHONY targets (jkonecny)
- Simplify archive creation (jkonecny)
- Reflect rename of COPYING to LICENSE.md (jkonecny)
- Change license in spec file to LGPLv3+ (jkonecny)
- Add COPR badge for daily builds (jkonecny)
- Improve setup.py (jkonecny)
- Change license to LGPLv3 for all the files (jkonecny)
- Change License from GPLv2+ to LGPLv3+ (jkonecny)
* Thu Aug 22 2019 Jiri Konecny <jkonecny@redhat.com> - 1.6-1
- Always close the password dialog (vponcova)
- Remove unnecessary pass statements (jkonecny)
* Mon May 13 2019 Jiri Konecny <jkonecny@redhat.com> - 1.5-1
- Update spec file from the downstream (jkonecny)
- Fix translation issue for lt language (jkonecny)
* Sat Feb 02 2019 Fedora Release Engineering <releng@fedoraproject.org> - 1.4-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Mon Nov 19 2018 Jiri Konecny <jkonecny@redhat.com> - 1.4-1
- Fix and add tests for the new changes (#1646568) (jkonecny)
- Add should_run_with_empty_stack configuration (#1646568) (jkonecny)
- Remove wrong line in password_function conf (jkonecny)
- Tweak date lang settings in make bumpver command (jkonecny)
- Drop python-pocketlint build dependency (jkonecny)
- Update spec file from Fedora (jkonecny)
* Sat Jul 14 2018 Fedora Release Engineering <releng@fedoraproject.org> - 1.3-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Mon Jul 02 2018 Miro Hrončok <mhroncok@redhat.com> - 1.3-4
- Rebuilt for Python 3.7
* Wed Jun 20 2018 Jiri Konecny <jkonecny@redhat.com> - 1.3-3
- Drop python-pocketlint dependency
* Mon Jun 18 2018 Miro Hrončok <mhroncok@redhat.com> - 1.3-2
- Rebuilt for Python 3.7
* Mon Jun 11 2018 Jiri Konecny <jkonecny@redhat.com> - 1.3-1
- Fix pylint errors raised by new pylint version (jkonecny)
- Setup logging handler properly for a library (jkonecny)
* Tue May 29 2018 Jiri Konecny <jkonecny@redhat.com> - 1.2-1
- Document GlobalConfiguration class (jkonecny)
- Add advanced input processing section to docs (jkonecny)
- Make makefile zanata check more robust (jkonecny)
- Add missing newline characters at the end (jkonecny)
- Abstract widget buffer extensions (jkonecny)
- Use python 3 sphinx module (jkonecny)
* Tue Apr 10 2018 Jiri Konecny <jkonecny@redhat.com> - 1.1-1
- Add global configuration initialize test (jkonecny)
- Use default password function from GlobalConfiguration (jkonecny)
- Add tests for GlobalConfiguration class (jkonecny)
- Move default width to GlobalConfiguration class (jkonecny)
- Add GlobalConfiguration object (jkonecny)
- Fix a missing article in docs. (jkonecny)
- Add tests for UIScreen get_user_input method (jkonecny)
- Only InputManager is used to get input in UIScreen (jkonecny)
- Move emit of InputReadySignal to InputRequest (jkonecny)
- Get only last input on concurrent input (jkonecny)
- Add concurrency tests for InputHandler (jkonecny)
- Add requester source to the InputHandler (#1557472) (jkonecny)
- Use InputManager as screen helper not in ScreenScheduler (#1557472) (jkonecny)
- Add test to check InputThreadManager after initialization (jkonecny)
- Reinitialize InputThreadManager when app is initialized (jkonecny)
- Input requests are now handled by InputThreadManager (jkonecny)
- Remove io_manager property from ScreenScheduler (jkonecny)
- Rename io_manager to input_manager (jkonecny)
- Change IOManager to InputManager (jkonecny)
- Move draw from IOManager to ScreenScheduler (jkonecny)
- Separate password input to PasswordInputHandler (jkonecny)
- Add DEFAULT_WIDTH constant (jkonecny)
- Add tests for InputHandler (jkonecny)
- Replace get_input_without_check by new property (jkonecny)
- Separate input to a new module (jkonecny)
* Wed Feb 28 2018 Jiri Konecny <jkonecny@redhat.com> - 1.0-1
- Fix docs based on the new PROCESSED feature (jkonecny)
- Use new PROCESS_AND* in examples (jkonecny)
- Add tests for PROCESSED_AND_{CLOSE|REDRAW} (jkonecny)
- Use new PROCESSED_AND_{CLOSE|REDRAW} in advanced widgets (jkonecny)
- Support PROCESSED_AND_CLOSE UserInputState (jkonecny)
- Rename UserInputResult to UserInputAction (jkonecny)
- Support PROCESSED_AND_REDRAW (jkonecny)
- Fix link to the documentation in README (jkonecny)
- Add link to the documentation (jkonecny)
- Write documentation for Simpleline (jkonecny)
- Disable pylint error for doc copyright variable (jkonecny)
- Add new example used in the documentation (jkonecny)
- Mock modules for readthedocs (jkonecny)
- Do a small fixes to improve documentation (jkonecny)
- Get version from the spec file (jkonecny)
- Add documentation skeleton (jkonecny)
* Thu Oct 19 2017 Jiri Konecny <jkonecny@redhat.com> - 0.8-1
- Test clean-up (jkonecny)
- Move and rename force_quit loop (jkonecny)
- Add dump screen stack method (jkonecny)
* Tue Oct 17 2017 Jiri Konecny <jkonecny@redhat.com> - 0.7-1
- Fix crash when container callback is not set (jkonecny)
- Add EntryWidget to show item title and value (jkonecny)
- Add GetPasswordInputScreen for getting passwords (jkonecny)
- UIScreen can have hidden input (jkonecny)
- Move getpass func inside locks (jkonecny)
- Implement GetInputScreen (jkonecny)
- Do not print new line with empty container (jkonecny)
- Add no_separator to UIScreen (jkonecny)
- Fix HelpScreen title (jkonecny)
- Remove original classes from GLib tests (jkonecny)
- Fix test case name (jkonecny)
- Move tests to subfolder (jkonecny)
- Move base widgets as second example (jkonecny)
- Add example with basic widgets usage (jkonecny)
- Improve README.md (jkonecny)
- Improve example starting script (jkonecny)
- Add glib loop example (jkonecny)
- Add prefixes to examples to impress difficulty (jkonecny)
- Add comments to the examples (jkonecny)
- Update examples to use new features (jkonecny)
* Fri Sep 08 2017 Jiri Konecny <jkonecny@redhat.com> - 0.6-1
- Implement the force quit all loops feature (jkonecny)
- Improved GLib event loop testing (jkonecny)
- Handle GLib event loop exceptions better (jkonecny)
- Support for GLib event loop (jkonecny)
- Wrap exceptions from handlers as ExceptionSignal (jkonecny)
- Only highest priority events are processed in one iteration (jkonecny)
- Move parts from MainLoop to AbstractEventLoop (jkonecny)
- MainLoop won't wait in busy loop anymore (jkonecny)
- Don't block loop when waiting on user input (jkonecny)
- Fix user input lock (jkonecny)
* Mon Sep 04 2017 Jiri Konecny <jkonecny@redhat.com> - 0.5-1
- Fix exception in Screen input caused infinite loop (jkonecny)
- Add test if app is initialized (jkonecny)
- Redraw on first scheduled screen (#1487326) (jkonecny)
- Redraw after YesNoDialog modal window close (jkonecny)
* Fri Aug 18 2017 Jiri Konecny <jkonecny@redhat.com> - 0.4-1
- Screen scheduling is moved to ScreenHandler (jkonecny)
- Remove merge commits from changelog (jkonecny)
* Thu Aug 17 2017 Jiri Konecny <jkonecny@redhat.com> - 0.3-1
- Remove merge commits from changelog (jkonecny)
- Fix bad input processing for list containers (jkonecny)
- Rename quit callback and add args there. (jkonecny)
- Add tests for new container feature (jkonecny)
- List containers take max width if not specified (jkonecny)
- Remove leftover print in containers parsing input (jkonecny)
* Mon Aug 14 2017 Jiri Konecny <jkonecny@redhat.com> - 0.2-1
- Merge pull request #26 from jkonecny12/master-fix-ExitMainLoop-exception (jkonecny)
- Merge pull request #28 from jkonecny12/master-fix-container-callback (jkonecny)
- Fix docs for container callbacks (jkonecny)
- Merge pull request #25 from jkonecny12/master-fix-big-screen-printing (jkonecny)
- Remove missed test helper for local testing (jkonecny)
- The ExitMainLoop exception should kill whole app (jkonecny)
- Do not run an old event queue from modal screen (jkonecny)
- Merge pull request #23 from jkonecny12/master-add-possibility-to-end-loop-politely (jkonecny)
- Merge pull request #24 from jkonecny12/master-fix-compatibility (jkonecny)
- Add possibility to end loop politely (jkonecny)
- Merge pull request #22 from jkonecny12/master-add-logging (jkonecny)
- Fix printing issues for bigger screens (jkonecny)
- Set default screen height to 30 (jkonecny)
- Keep backward compatibility for UIScreen args (jkonecny)
- Add logging to the Simpleline (jkonecny)
- Merge pull request #19 from jkonecny12/master-rework-wait-on-input (jkonecny)
- Merge pull request #21 from jkonecny12/master-fix-adv-widgets (jkonecny)
- Merge pull request #20 from jkonecny12/master-move-InputState (jkonecny)
- Wait on input thread to finish (jkonecny)
- return_after don't skip signals in recursion (jkonecny)
- Add TicketMachine helper class (jkonecny)
- Return user input in the signal handler (jkonecny)
- Merge pull request #16 from jkonecny12/master-modify-exception-handling (jkonecny)
- Merge pull request #18 from jkonecny12/master-ignore-pylint-with-lock (jkonecny)
- Reflect changes in adv_widgets (jkonecny)
- Move InputState next to UIScreen (jkonecny)
- Break App's cyclic imports (jkonecny)
- Add temporal pylint-disable for with Lock (jkonecny)
- Re-raise exception only if no handler is registered (jkonecny)
- Merge pull request #15 from jkonecny12/master-thread-safe-eventqueue (jkonecny)
- Merge pull request #11 from jkonecny12/master-add-args-to-scheduler-shortcut (jkonecny)
- Make the EventQueue thread safe (jkonecny)
- Merge pull request #13 from jkonecny12/master-enhance-WindowContainer-add (jkonecny)
- Merge pull request #12 from jkonecny12/master-fix-ready-collision (jkonecny)
- Add args param to the scheduler_handler shortcuts (jkonecny)
- Fix bad param name for scheduling methods (jkonecny)
- Merge pull request #10 from jkonecny12/master-add-hidden-user-input (jkonecny)
- Merge pull request #8 from jkonecny12/master-remove-base-file (jkonecny)
- Merge pull request #9 from jkonecny12/master-remove-quit-message (jkonecny)
- Add method add_with_separator to WindowContainer (jkonecny)
- Fix `ready` property collision with Anaconda (jkonecny)
- Add hidden parameter to UIScreen get input (jkonecny)
- Remove unused quit message (jkonecny)
- Move App from base to __init__.py (jkonecny)
- Merge pull request #6 from jkonecny12/fix-make-potfile-generation (jkonecny)
- Merge pull request #7 from jkonecny12/master-fix-make-archive (jkonecny)
- Fix make archive (jkonecny)
- Fix potfile generation (jkonecny)
- Merge pull request #5 from jkonecny12/master-add-containers (jkonecny)
- Examples now using WindowContainer (jkonecny)
- Fix error when getting input directly (jkonecny)
- Add WindowContainer and use it in UIScreen (jkonecny)
- Add SeparatorWidget (jkonecny)
- Add input processing to containers (jkonecny)
- Add containers numbering (jkonecny)
- Add containers, checkbox and center widget tests (jkonecny)
- Add row and column list containers (jkonecny)
- Merge pull request #4 from jkonecny12/master-split-renderer (jkonecny)
- Fix setup.py modules (jkonecny)
- Change INPUT_PROCESSED and INPUT_DISCARDED to Enum (jkonecny)
- Rename renderer and event_loop in App class (jkonecny)
- Add scheduler_handler and rename switch_screen (jkonecny)
- Process signals when waiting on user input (jkonecny)
- Move draw screen to the InOutManager (jkonecny)
- Change UIScreen properties (jkonecny)
- Move input processing to the InOutManager class (jkonecny)
- Move SignalHandler to the screen module (jkonecny)
- Move ui_screen to the screen module (jkonecny)
- Rename Renderer to ScreenScheduler (jkonecny)
- Merge pull request #3 from jkonecny12/master-change-close-to-signal (jkonecny)
- Change separator tests to reflect close as signal (jkonecny)
- Close screen is now signal (jkonecny)
- Rename emit_draw_signal to redraw (jkonecny)
- Merge pull request #2 from jkonecny12/master-fix-modal (jkonecny)
- Add missing doc for EventQueue and ScreeStack (jkonecny)
- Add tests for event queue (jkonecny)
- Modal screens are now executing new event loop (jkonecny)
- Separator should be printed just before show_all() (jkonecny)
- Move screen scheduling tests to new file (jkonecny)
- Merge pull request #1 from jkonecny12/master-refactorization (jkonecny)
- Implement small fixes and tweaks (jkonecny)
- Move ScreenStack to render module (jkonecny)
- Add SimplelineError as base class for Exceptions (jkonecny)
- Clean up unused code and do a small tweaks (jkonecny)
- Fix pylint errors (jkonecny)
- Modify examples to reflect new changes (jkonecny)
- Add emit_draw_signal to the SignalHandler (jkonecny)
- Do not call draw when prompt is not present (jkonecny)
- Remove old INPUT_* constants (jkonecny)
- Do not redraw when input was processed (jkonecny)
- Add license to all test classes (jkonecny)
- Add new tests for render_screen, renderer, widgets (jkonecny)
- Remove hubQ (jkonecny)
- Split input from drawing (jkonecny)
- Fix rendering errors by removing _redraw variable (jkonecny)
- Replace array with priority by Signal ordering (jkonecny)
- Add tests for SignalHandler (jkonecny)
- Use SignalHandler (jkonecny)
- Add screen rendering tests (jkonecny)
- Raise exceptions instead of silently quitting (jkonecny)
- Reflect code changes in tests (jkonecny)
- Fix missing super().__init__() in Signals (jkonecny)
- Fix modal screen by removing execute_new_loop (jkonecny)
- Fail when UIScreen is not closing itself (jkonecny)
- Add constants INPUT_PROCESSED and INPUT_DISCARDED (jkonecny)
- Fix issues in Renderer and MainLoop (jkonecny)
- Use RenderScreenSignal signal for rendering (jkonecny)
- Add App.run as a shortcut to start application (jkonecny)
- Move widgets and adv_widgets to render module (jkonecny)
- Fix cosmetic bugs in widgets (jkonecny)
- Move prompt to render module (jkonecny)
- Modify ScreenStack tests to reflect UIScreen changes (jkonecny)
- Modify UIScreen to use new App class structure (jkonecny)
- Fix renderer tests after moving the Renderer class (jkonecny)
- Move renderer to separate file renderer.py (jkonecny)
- Add quit_callback to the event loop abstract class (jkonecny)
- Add tests for the App class (jkonecny)
- Change base class to singleton like class (jkonecny)
- Add tests for event loop processing (jkonecny)
- Implementation of defualt event loop and signals (jkonecny)
- Add abstract base classes for EventLoop and Event (jkonecny)
- Remove ScheduleScreen tests (jkonecny)
- Add tests for renderer (jkonecny)
- Extract renderer from App class to render directory (jkonecny)
- Add tests for new ScreenStack and ScreenData (jkonecny)
- Extract screen stack to its own object and data class (jkonecny)
- Modify comments in the spec file (jkonecny)
- Package is named python-simpleline (jkonecny)
- Fix spec file (jkonecny)
- Fix summary in the spec file (jkonecny)
- Fix names according to tagging (jkonecny)
* Fri May 5 2017 Jiri Konecny <jkonecny@redhat.com> - 0.1-3
- Modify comments in the spec file
* Thu May 4 2017 Jiri Konecny <jkonecny@redhat.com> - 0.1-2
- Drop clean section
- Drop Group, it is not needed
- Use make_build macro
- Reorder check and install sections
- Rename package to python-simpleline but rpm will be python3-simpleline
* Fri Dec 16 2016 Jiri Konecny <jkonecny@redhat.com> - 0.1-1
- Initial package
@@ -0,0 +1,43 @@
#!/usr/bin/python3
#
# 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 setuptools import setup, find_packages
with open("README.md", "r") as f:
long_description = f.read()
setup(name='simpleline',
version='1.8',
author='Jiri Konecny',
author_email='jkonecny@redhat.com',
description='Python text UI framework',
long_description=long_description,
long_description_content_type="text/markdown",
keywords='ui text library glib',
url='https://github.com/rhinstaller/python-simpleline',
packages=find_packages(include=['simpleline', 'simpleline.*']),
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Software Development :: User Interfaces",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
"Programming Language :: Python :: 3",
],
python_requires='>=3.4')
@@ -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()

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