[Tutor] clearing lines for a 'front end' to a tool

James jtp at nc.rr.com
Wed Sep 17 23:02:48 CEST 2008


Kent / Alan,

Thanks for the responses. I'm not completely certain that urwid is
appropriate for the program I'm writing, as it seems to be more of a
framework for developing a text GUI application. (similar to curses?)

The behavior of the program I'm writing is actually identical to that
of an open source product. I asked the developers of the project where
I could find the code that actually produced the output that I'm
mimicking, and he pointed me to the correct portion of the open source
code. Below is the class that generates the output.

-----

class JobStatusDisplay(object):

        _bound_properties = ("curval", "failed", "running")
        _jobs_column_width = 48

        # Don't update the display unless at least this much
        # time has passed, in units of seconds.
        _min_display_latency = 2

        _default_term_codes = {
                'cr'  : '\r',
                'el'  : '\x1b[K',
                'nel' : '\n',
        }

        _termcap_name_map = {
                'carriage_return' : 'cr',
                'clr_eol'         : 'el',
                'newline'         : 'nel',
        }

        def __init__(self, out=sys.stdout, quiet=False):
                object.__setattr__(self, "out", out)
                object.__setattr__(self, "quiet", quiet)
                object.__setattr__(self, "maxval", 0)
                object.__setattr__(self, "merges", 0)
                object.__setattr__(self, "_changed", False)
                object.__setattr__(self, "_displayed", False)
                object.__setattr__(self, "_last_display_time", 0)
                self.reset()

                isatty = hasattr(out, "isatty") and out.isatty()
                object.__setattr__(self, "_isatty", isatty)
                if not isatty or not self._init_term():
                        term_codes = {}
                        for k, capname in self._termcap_name_map.iteritems():
                                term_codes[k] =
self._default_term_codes[capname]
                        object.__setattr__(self, "_term_codes", term_codes)

        def _init_term(self):
                """
                Initialize term control codes.
                @rtype: bool
                @returns: True if term codes were successfully initialized,
                        False otherwise.
                """

                term_type = os.environ.get("TERM", "vt100")
                tigetstr = None

                try:
                        import curses
                        try:
                                curses.setupterm(term_type, self.out.fileno())
                                tigetstr = curses.tigetstr
                        except curses.error:
                                pass
                except ImportError:
                        pass

                if tigetstr is None:
                        return False

                term_codes = {}
                for k, capname in self._termcap_name_map.iteritems():
                        code = tigetstr(capname)
                        if code is None:
                                code = self._default_term_codes[capname]
                        term_codes[k] = code
                object.__setattr__(self, "_term_codes", term_codes)
                return True

        def _format_msg(self, msg):
                return ">>> %s" % msg

        def _erase(self):
                self.out.write(
                        self._term_codes['carriage_return'] + \
                        self._term_codes['clr_eol'])
                self._displayed = False

        def _display(self, line):
                self.out.write(line)
                self._displayed = True

        def _update(self, msg):

                out = self.out
                if not self._isatty:
                        out.write(self._format_msg(msg) +
self._term_codes['newline'])
                        return

                if self._displayed:
                        self._erase()

                self._display(self._format_msg(msg))


        def displayMessage(self, msg):

                was_displayed = self._displayed

                if self._isatty and self._displayed:
                        self._erase()

                self.out.write(self._format_msg(msg) +
self._term_codes['newline'])
                self._displayed = False

                if was_displayed:
                        self._changed = True
                        self.display()

        def reset(self):
                self.maxval = 0
                self.merges = 0
                for name in self._bound_properties:
                        object.__setattr__(self, name, 0)

                if self._displayed:
                        self.out.write(self._term_codes['newline'])
                        self._displayed = False

        def __setattr__(self, name, value):
                old_value = getattr(self, name)
                if value == old_value:
                        return
                object.__setattr__(self, name, value)
                if name in self._bound_properties:
                        self._property_change(name, old_value, value)

        def _property_change(self, name, old_value, new_value):
                self._changed = True
                self.display()

        def _load_avg_str(self, digits=2):
                try:
                        avg = os.getloadavg()
                except OSError, e:
                        return str(e)
                return ", ".join(fpformat.fix(x, digits) for x in avg)

        def _display_status(self):
                # Don't use len(self._completed_tasks) here since that also
                # can include uninstall tasks.
                curval_str = str(self.curval)
                maxval_str = str(self.maxval)
                running_str = str(self.running)
                failed_str = str(self.failed)
                load_avg_str = self._load_avg_str()

                color_output = StringIO.StringIO()
                plain_output = StringIO.StringIO()
                style_file = portage.output.ConsoleStyleFile(color_output)
                style_file.write_listener = plain_output
                style_writer =
portage.output.StyleWriter(file=style_file, maxcol=9999)
                style_writer.style_listener = style_file.new_styles
                f = formatter.AbstractFormatter(style_writer)

                number_style = "INFORM"
                f.add_literal_data("Jobs: ")
                f.push_style(number_style)
                f.add_literal_data(curval_str)
                f.pop_style()
                f.add_literal_data(" of ")
                f.push_style(number_style)
                f.add_literal_data(maxval_str)
                f.pop_style()
                f.add_literal_data(" complete")

                if self.running:
                        f.add_literal_data(", ")
                        f.push_style(number_style)
                        f.add_literal_data(running_str)
                        f.pop_style()
                        f.add_literal_data(" running")

                if self.failed:
                        f.add_literal_data(", ")
                        f.push_style(number_style)
                        f.add_literal_data(failed_str)
                        f.pop_style()
                        f.add_literal_data(" failed")

                padding = self._jobs_column_width - len(plain_output.getvalue())
                if padding > 0:
                        f.add_literal_data(padding * " ")

                f.add_literal_data("Load avg: ")
                f.add_literal_data(load_avg_str)

                self._update(color_output.getvalue())
                xtermTitle(" ".join(plain_output.getvalue().split()))

-----

I don't see anything in the code that particularly "stands out" on how
to go about generating the output that I'd like. I do see, however,
that a curses object is being instantiated.

I think I'm a bit in over my head this time. ;) I did look through
some of the curses documentation on the web and I didn't see much on
how to clear lines. I don't really want to set up a "text GUI"
application (where there are windows and menus at the top); I just
want more flexibility in outputting text to the console.

btw, Alan, I'm using Linux. :)

-j

On Mon, Sep 15, 2008 at 8:42 PM, Alan Gauld <alan.gauld at btinternet.com> wrote:
> "James" <jtp at nc.rr.com> wrote
>
>> I'm writing a 'front end' which will show a list of the "things" that
>> are currently happening in the background in a simple
>> (non-interactive) interface. Here's an idea of what the terminal will
>> look like when the program is running.
>
> The critical piece of missing info is which platform/OS we
> are dealing with. Anyy kind of terminal control is going to be
> platform dependant - which is why curses may not be overkill!
> (except it doesn't work well on Windows)
>
>> The question here is mostly about how to generate this output. The
>> bottom line (the 'status') *always* remains at the bottom of the
>
> This is where terminal control comes in and a lot depends on
> what terninal you are using.
>
>> I'm unsure how to go about doing this... I imagine that the ANSI
>> "clear line" value may be helpful
>
> That would work on Windows but most *nix consoles don't use
> the ANSI codes consistently, most will be haoppier with DEC VT
> control codes (or Tektronics or Wyse). Thats where curses helps
> by hiding all that stuff.
>
> If on *nix use curses or any of several other console UI  libraries.
> On Windows use native ANSI codes and/or the Conio library.
>
> Several other options here:
>
> http://py.vaults.ca/parnassus/apyllo.py/808292924.243256747
>
> Although many may be outdated now.
>
> Finally, this would be very easy to do in a Tkinter GUI.
> All you need is a containing frame, a text widget and a label and
> either a timer or a callback structure from your threads bto trigger
> updates. Probably easier than using a terninal interface!
>
> HTH,
>
> --
> Alan Gauld
> Author of the Learn to Program web site
> http://www.freenetpages.co.uk/hp/alan.gauld
>
> _______________________________________________
> Tutor maillist  -  Tutor at python.org
> http://mail.python.org/mailman/listinfo/tutor
>


More information about the Tutor mailing list