[PATCH 07/13] Add documentation to the simpleline library for TUI
Martin Sivak
msivak at redhat.com
Wed Aug 8 14:22:24 UTC 2012
You mean the :param? It is the syntax for the tool Python itself uses (Sphinx) as I pointed out in the cover letter.
We are not generating any docs atm and I just looked at all the tools available - most notably Doxygen, epydoc and Sphinx and this seems to be the best way.. it is nice, makes type explicit and supports Python and C/C++ :)
Martin
----- Original Message -----
> Any reason for the different syntax of the docstrings?
>
> On Wed, 2012-08-08 at 10:52 +0200, Martin Sivak wrote:
> > ---
> > pyanaconda/ui/tui/simpleline/base.py | 294
> > +++++++++++++++++++++++++------
> > pyanaconda/ui/tui/simpleline/widgets.py | 87 ++++++++-
> > 2 files changed, 323 insertions(+), 58 deletions(-)
> >
> > diff --git a/pyanaconda/ui/tui/simpleline/base.py
> > b/pyanaconda/ui/tui/simpleline/base.py
> > index 573f28b..0c9f750 100644
> > --- a/pyanaconda/ui/tui/simpleline/base.py
> > +++ b/pyanaconda/ui/tui/simpleline/base.py
> > @@ -19,19 +19,47 @@
> > # Red Hat Author(s): Martin Sivak <msivak at redhat.com>
> > #
> >
> > -__all__ = ["ExitMainLoop", "App", "UIScreen", "Widget"]
> > +__all__ = ["App", "UIScreen", "Widget"]
> >
> > import readline
> >
> > class ExitAllMainLoops(Exception):
> > + """This exceptions ends the whole App mainloop structure.
> > App.run() quits
> > + after it is processed."""
> > pass
> >
> > class ExitMainLoop(Exception):
> > + """This eceptions ends the outermost mainloop. Used internally
> > when dialogs
> > + close."""
> > pass
> >
> >
> > class App(object):
> > + """This is the main class for TUI screen handling. It is
> > responsible for
> > + mainloop control and keeping track of the screen stack.
> > +
> > + Screens are organized in stack structure so it is possible
> > to return
> > + to caller when dialog or sub-screen closes.
> > +
> > + It supports four window transitions:
> > + - show new screen replacing the current one (linear
> > progression)
> > + - show new screen keeping the current one in stack (hub &
> > spoke)
> > + - show new screen and wait for it to end (dialog)
> > + - close current window and return to the next one in stack
> > + """
> > +
> > def __init__(self, title, yes_or_no_question = None, width =
> > 80):
> > + """
> > + :param title: application title for whenewer we need to
> > display app name
> > + :type title: unicode
> > +
> > + :param yes_or_no_question: UIScreen object class used for
> > Quit dialog
> > + :type yes_or_no_question: class UIScreen accepting
> > additional message arg
> > +
> > + :param width: screen width for rendering purposes
> > + :type width: int
> > + """
> > +
> > self._header = title
> > self._spacer = "\n".join(2*[width*"="])
> > self._width = width
> > @@ -40,36 +68,90 @@ class App(object):
> > # screen stack contains triplets
> > # UIScreen to show
> > # arguments for it's show method
> > - # value indicating whether new mainloop is needed - None
> > = do nothing, True = execute, False = already running, exit when
> > window closes
> > + # value indicating whether new mainloop is needed
> > + # - None = do nothing
> > + # - True = execute new loop
> > + # - False = already running loop, exit when window
> > closes
> > self._screens = []
> >
> > def switch_screen(self, ui, args = None):
> > - """Schedules a screen to replace the current one."""
> > + """Schedules a screen to replace the current one.
> > +
> > + :param ui: screen to show
> > + :type ui: instance of UIScreen
> > +
> > + :param args: optional argument to pass to ui's refresh
> > method (can be used to select what item should be displayed or so)
> > + :type args: anything
> > +
> > + """
> > +
> > oldscr, oldattr, oldloop = self._screens.pop()
> > +
> > + # we have to keep the oldloop value so we stop
> > + # dialog's mainloop if it ever uses switch_screen
> > self._screens.append((ui, args, oldloop))
> > self.redraw()
> >
> > def switch_screen_with_return(self, ui, args = None):
> > - """Schedules a screen to show, but keeps the current one
> > in stack to return to, when the new one is closed."""
> > + """Schedules a screen to show, but keeps the current one
> > in stack
> > + to return to, when the new one is closed.
> > +
> > + :param ui: screen to show
> > + :type ui: UIScreen instance
> > +
> > + :param args: optional argument, please see switch_screen
> > for details
> > + :type args: anything
> > + """
> > +
> > self._screens.append((ui, args, None))
> > self.redraw()
> >
> > def switch_screen_modal(self, ui, 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."""
> > + """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 to show
> > + :type ui: UIScreen instance
> > +
> > + :param args: optional argument, please see switch_screen
> > for details
> > + :type args: anything
> > + """
> > +
> > + # set the third item to True so new loop gets started
> > self._screens.append((ui, args, True))
> > self._do_redraw()
> >
> > def schedule_screen(self, ui, args = None):
> > - """Add screen to the bottom of the stack."""
> > + """Add screen to the bottom of the stack. This is mostly
> > usefull
> > + at the beginning to prepare the first screen hierarchy to
> > display.
> > +
> > + :param ui: screen to show
> > + :type ui: UIScreen instance
> > +
> > + :param args: optional argument, please see switch_screen
> > for details
> > + :type args: anything
> > + """
> > self._screens.insert(0, (ui, args, None))
> >
> > def close_screen(self, scr = None):
> > + """Close the currently displayed screen and exit it's main
> > loop
> > + if necessary. Next screen from the stack is then
> > displayed.
> > +
> > + :param scr: if an UIScreen instance is passed it is
> > checked to be the screen we are trying to close.
> > + :type scr: UIScreen instance
> > + """
> > +
> > oldscr, oldattr, oldloop = self._screens.pop()
> > if scr is not None:
> > assert oldscr == scr
> >
> > + # this cannot happen, if we are closing the window,
> > + # the loop must have been running or not be there at all
> > + assert oldloop != True
> > +
> > # we are in modal window, end it's loop
> > - assert oldloop != True # this cannot happen, if we are
> > closing the window, the loop must be running or not there
> > if oldloop == False:
> > raise ExitMainLoop()
> >
> > @@ -80,19 +162,32 @@ class App(object):
> >
> > def _do_redraw(self):
> > """Draws the current screen and returns True if user input
> > is requested.
> > - If modal screen is requested, starts a new loop and
> > initiates redraw after it ends."""
> > + If modal screen is requested, starts a new loop and
> > initiates redraw after it ends.
> > +
> > + :return: this method returns True if user input
> > processing is requested
> > + :rtype: bool
> > + """
> > +
> > + # there is nothing to display, exit
> > if not self._screens:
> > raise ExitMainLoop()
> >
> > + # get the screen from the top of the stack
> > screen, args, newloop = self._screens[-1]
> >
> > + # new mainloop is requested
> > if newloop == True:
> > + # change the record to indicate mainloop is running
> > self._screens.pop()
> > self._screens.append((screen, args, False))
> > + # start the mainloop
> > self.mainloop()
> > + # after the mainloop ends, set the redraw flag
> > + # and skip the input processing once, to redisplay the
> > screen first
> > self.redraw()
> > - input_needed = False # we have to skip input once, to
> > redisplay the screen first
> > + input_needed = False
> > else:
> > + # get the widget tree from the screen and show it in
> > the screen
> > input_needed = screen.refresh(args)
> > screen.window.show_all()
> > self._redraw = False
> > @@ -100,57 +195,99 @@ class App(object):
> > return input_needed
> >
> > 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 mainloops
> > + working."""
> > +
> > try:
> > self.mainloop()
> > except ExitAllMainLoops:
> > pass
> >
> > def mainloop(self):
> > + """Single mainloop. Do not use directly, start the
> > application using run()."""
> > +
> > + # ask for redraw by default
> > self._redraw = True
> > +
> > + # inital state
> > last_screen = None
> > error_counter = 0
> > +
> > + # run until there is nothing else to display
> > while self._screens:
> > + # if redraw is needed, separate the content on the
> > screen from the
> > + # stuff we are about to display now
> > if self._redraw:
> > print self._spacer
> >
> > try:
> > + # draw the screen if redraw is needed or the
> > screen changed
> > + # (unlikely to happen separately, but just be
> > sure)
> > if self._redraw or last_screen !=
> > self._screens[-1]:
> > + # we have fresh screen, reset error counter
> > + error_counter = 0
> > if not self._do_redraw():
> > + # if no input processing is requested, go
> > for another cycle
> > continue
> >
> > last_screen = self._screens[-1][0]
> >
> > + # get the screen's prompt
> > prompt = last_screen.prompt()
> > +
> > + # None means prompt handled the input by itself
> > + # ask for redraw and continue
> > if prompt is None:
> > self.redraw()
> > continue
> >
> > + # get the input from user
> > c = raw_input(prompt)
> >
> > + # process the input, if it wasn't processed
> > (valid)
> > + # increment the error counter
> > if not self.input(c):
> > error_counter += 1
> >
> > + # redraw the screen after 5 bad inputs
> > if error_counter >= 5:
> > self.redraw()
> >
> > - if self._redraw:
> > - error_counter = 0
> > + # end just this loop
> > except ExitMainLoop:
> > break
> > +
> > + # propagate higher to end all loops
> > + # not really needed here, but we might need
> > + # more processing in the future
> > except ExitAllMainLoops:
> > raise
> >
> > def input(self, key):
> > - """Method called to process unhandled input key
> > presses."""
> > + """Method called internally to process unhandled input key
> > presses.
> > + Also handles the main quit and close commands.
> > +
> > + :param key: the string entered by user
> > + :type key: unicode
> > +
> > + :return: True if key was processed, False if it was not
> > recognized
> > + :rtype: True|False
> > +
> > + """
> > +
> > + # delegate the handling to active screen first
> > if self._screens:
> > key = self._screens[-1][0].input(key)
> > if key is None:
> > return True
> >
> > + # global close command
> > if self._screens and (key == 'c'):
> > self.close_screen()
> > return True
> >
> > + # global quit command
> > elif self._screens and (key == 'q'):
> > if self.quit_question:
> > d = self.quit_question(self, u"Do you really want
> > to quit?")
> > @@ -162,6 +299,7 @@ class App(object):
> > return False
> >
> > def redraw(self):
> > + """Set the redraw flag so the screen is refreshed as soon
> > as possible."""
> > self._redraw = True
> >
> > @property
> > @@ -169,29 +307,51 @@ class App(object):
> > return self._header
> >
> > @property
> > - def store(self):
> > - return self._store
> > -
> > - @property
> > def width(self):
> > + """Return the total width of screen space we have
> > available."""
> > return self._width
> >
> > class UIScreen(object):
> > + """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."""
> > +
> > + # title line of the screen
> > title = u"Screen.."
> >
> > def __init__(self, app):
> > + """
> > + :param app: reference to application main class
> > + :type app: instance of class App
> > + """
> > +
> > self._app = app
> > +
> > + # list that holds the content to be printed out
> > self._window = []
> >
> > def refresh(self, args = None):
> > - """Method which prepares the screen to self._window. If
> > user input is requested, return True."""
> > + """Method which prepares the content desired on the screen
> > to self._window.
> > +
> > + :param args: optional argument passed from switch_screen
> > calls
> > + :type args: anything
> > +
> > + :return: has to return True if input processing is
> > requested, otherwise
> > + the screen will get printed and the main loop
> > will continue
> > + :rtype: True|False
> > + """
> > +
> > self._window = [self.title, u""]
> > + return True
> >
> > @property
> > def window(self):
> > + """Return reference to the window instance. In TUI, just
> > return self."""
> > return self
> >
> > def show_all(self):
> > + """Prepares all elements of self._window for output and
> > then prints
> > + them on the screen."""
> > +
> > for w in self._window:
> > if hasattr(w, "render"):
> > w.render(self.app.width)
> > @@ -200,32 +360,49 @@ class UIScreen(object):
> > show = show_all
> >
> > def hide(self):
> > + """This does nothing in TUI, it is here to make API
> > similar."""
> > pass
> >
> > def input(self, key):
> > - """Method called to process input. If the input is not
> > handled here, return it."""
> > + """Method called to process input. If the input is not
> > handled here, return it.
> > +
> > + :param key: input string to process
> > + :type key: unicode
> > +
> > + :return: return True or None if key was handled, False if
> > the screen should not
> > + process input on the App and key if you want it
> > to.
> > + :rtype: True|False|None|unicode
> > + """
> > +
> > return key
> >
> > def prompt(self):
> > - """Return the text to be shown as prompt or handle the
> > prompt and return None."""
> > + """Return the text to be shown as prompt or handle the
> > prompt and return None.
> > +
> > + :return: returns text to be shown next to the prompt for
> > input or None
> > + to skip further input processing
> > + :rtype: unicode|None
> > +"""
> > return u"\tPlease make your choice from above ['q' to
> > quit]: "
> >
> > @property
> > def app(self):
> > + """The reference to this Screen's assigned App
> > instance."""
> > return self._app
> >
> > def close(self):
> > + """Close the current screen."""
> > self.app.close_screen(self)
> >
> > class Widget(object):
> > def __init__(self, max_width = None, default = None):
> > """Initializes base Widgets buffer.
> >
> > - @param max_width server as a hint about screen size to
> > write method with default arguments
> > - @type max_width int
> > + :param max_width: server 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
> > + :param default: string containing the default content
> > to fill the buffer with
> > + :type default: string
> > """
> >
> > self._buffer = []
> > @@ -236,10 +413,13 @@ class Widget(object):
> >
> > @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 reduce(lambda acc,l: max(acc, len(l)),
> > self._buffer, 0)
> >
> > def clear(self):
> > @@ -255,8 +435,8 @@ class Widget(object):
> > def render(self, width = None):
> > """This method has to redraw the widget's self._buffer.
> >
> > - @param width the width of buffer requested by the
> > caller
> > - @type width int
> > + :param width: the width of buffer requested by the
> > caller
> > + :type width: int
> >
> > This method will commonly call render of child widgets
> > and then draw and write
> > methods to copy their contents to self._buffer
> > @@ -264,10 +444,18 @@ class Widget(object):
> > self.clear()
> >
> > def __unicode__(self):
> > + """Method to render the screen when printing as unicode
> > string."""
> > return u"\n".join([u"".join(l) for l in self._buffer])
> >
> > def setxy(self, row, col):
> > - """Sets cursor position."""
> > + """Sets 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
> > @@ -281,32 +469,33 @@ class Widget(object):
> > def draw(self, w, row = None, col = None, block = False):
> > """This method copies 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 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 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 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 boolean
> > + :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
> > + # fill up rows to accomodate for w.height
> > if self.height < row + w.height:
> > for i in range(row + w.height - self.height):
> > self._buffer.append(list())
> >
> > - # append columns
> > + # append columns to accomodate for w.width
> > for l in range(row, row + w.height):
> > l_len = len(self._buffer[l])
> > w_len = len(w.content[l - row])
> > @@ -314,6 +503,7 @@ class Widget(object):
> > 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:
> > @@ -322,20 +512,20 @@ class Widget(object):
> > def write(self, text, row = None, col = None, width = None,
> > block = False):
> > """This method emulates typing machine writing to this
> > widget's buffer.
> >
> > - @param text text to type
> > - @type text unicode
> > + :param text: text to type
> > + :type text: unicode
> >
> > - @param row row number to start at (default is at the
> > cursor position)
> > - @type row int
> > + :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 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 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 boolean
> > + :param block: when printing newline, start at column
> > col (True) or at column 0 (False)
> > + :type block: boolean
> > """
> >
> > if row is None:
> > @@ -384,12 +574,12 @@ class Widget(object):
> >
> > self._cursor = (x, y)
> >
> > -class HelloWorld(UIScreen):
> > - def show(self, args = None):
> > - print """Hello World\nquit by typing 'quit'"""
> > - return True
> > -
> > if __name__ == "__main__":
> > + class HelloWorld(UIScreen):
> > + def show(self, args = None):
> > + print """Hello World\nquit by typing 'quit'"""
> > + return True
> > +
> > a = App("Hello World")
> > s = HelloWorld(a, None)
> > a.schedule_screen(s)
> > diff --git a/pyanaconda/ui/tui/simpleline/widgets.py
> > b/pyanaconda/ui/tui/simpleline/widgets.py
> > index 8dde043..d6ef5c6 100644
> > --- a/pyanaconda/ui/tui/simpleline/widgets.py
> > +++ b/pyanaconda/ui/tui/simpleline/widgets.py
> > @@ -26,20 +26,49 @@ __all__ = ["TextWidget", "ColumnWidget",
> > "CheckboxWidget", "CenterWidget"]
> > import base
> >
> > class TextWidget(base.Widget):
> > + """Class to handle wrapped text output."""
> > +
> > def __init__(self, text):
> > + """
> > + :param text: text to format
> > + :type text: unicode
> > + """
> > +
> > base.Widget.__init__(self)
> > self._text = 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
> > +
> > + :raises
> > + """
> > +
> > base.Widget.render(self, width)
> > self.write(self._text, width = width)
> >
> > class CenterWidget(base.Widget):
> > + """Class to handle horizontal centering of content."""
> > +
> > def __init__(self, w):
> > + """
> > + :param w: widget to center
> > + :type w: base.Widget
> > + """
> > base.Widget.__init__(self)
> > 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
> > + """
> > +
> > base.Widget.render(self, width)
> > self._w.render(width)
> > self.draw(self._w, col = (width - self._w.width) / 2)
> > @@ -48,11 +77,11 @@ class ColumnWidget(base.Widget):
> > def __init__(self, columns, spacing = 0):
> > """Create text columns
> >
> > - @param columns list containing (column width, [list of
> > widgets to put into this column])
> > - @type columns [(int, [...]), ...]
> > + :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 int
> > + :param spacing: number of spaces to use between columns
> > + :type spacing: int
> > """
> >
> > base.Widget.__init__(self)
> > @@ -60,26 +89,60 @@ class ColumnWidget(base.Widget):
> > 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
> > + :rtype: None
> > + """
> > +
> > base.Widget.render(self, width)
> >
> > + # the lefmost empty column
> > x = 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.setxy(0, x)
> >
> > + # 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
> > x = max((x + col_width), self.width) + self._spacing
> >
> > class CheckboxWidget(base.Widget):
> > - def __init__(self, key = None, title = None, text = None,
> > completed = None):
> > + """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: unicode
> > +
> > + :param text: the description text to be shown on the
> > second row in ()
> > + :type text: unicode
> > +
> > + :param completed: is the checkbox ticked or not?
> > + :type completed: True|False
> > + """
> > +
> > base.Widget.__init__(self)
> > self._key = key
> > self._title = title
> > @@ -87,37 +150,49 @@ class CheckboxWidget(base.Widget):
> > self._completed = completed
> >
> > def render(self, width):
> > + """Render the widget to internal buffer. It should be max
> > width
> > + characters wide."""
> > base.Widget.render(self, width)
> >
> > if self.completed:
> > - checkchar = "x"
> > + 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
> >
> > if __name__ == "__main__":
>
> --
> Vratislav Podzimek
>
> Anaconda Rider | Red Hat, Inc. | Brno - Czech Republic
>
> _______________________________________________
> anaconda-patches mailing list
> anaconda-patches at lists.fedorahosted.org
> https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
>
More information about the anaconda-patches
mailing list