feat(Anaconda): Local Repo

This commit is contained in:
2026-06-13 02:06:34 +02:00
parent 05ae9c36c3
commit c412cd5d33
203 changed files with 23997 additions and 7 deletions
@@ -0,0 +1,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