From 1d4403e3081c569c7f0f5cd63b94662b73b1f4cf Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 18 Feb 2013 18:00:16 +0100 Subject: [PATCH 1/5] Let's remove that synchronous functionality alltogether. It simply isn't in the spirit of this app :-) And nobody uses it. --- i3pystatus/__init__.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index 6d6f9fa..1f8d19d 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -7,19 +7,13 @@ from threading import Thread import time from contextlib import contextmanager -class BaseModule: +class Module: output = None def registered(self, status_handler): """Called when this module is registered with a status handler""" - def tick(self): - """Called once per tick""" - -class Module(BaseModule): - pass - -class AsyncModule(BaseModule): +class AsyncModule(Module): def registered(self, status_handler): self.thread = Thread(target=self.mainloop) self.thread.daemon = True @@ -80,7 +74,6 @@ class JSONIO: j = json.loads(line) yield j - self.io.write(prefix + json.dumps(j)) class I3statusHandler: @@ -104,8 +97,6 @@ class I3statusHandler: while True: with jio.read() as j: for module in self.modules: - module.tick() - output = module.output if output: j.insert(0, output) From 0ad4ae6a026de169c5fec18fc25471f5d16d53fb Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 18 Feb 2013 18:02:23 +0100 Subject: [PATCH 2/5] This isn't a program... this is a poem! --- i3pystatus/__init__.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index 1f8d19d..3063c6e 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -64,8 +64,13 @@ class JSONIO: self.io.write(self.io.read()) self.io.write(self.io.read()) - @contextmanager def read(self): + while True: + with self.read_line() as j: + yield j + + @contextmanager + def read_line(self): line, prefix = self.io.read(), "" # ignore comma at start of lines @@ -83,7 +88,7 @@ class I3statusHandler: if fd is None: fd = sys.stdin - self.io = IOHandler(fd) + self.io = JSONIO(IOHandler(fd)) def register(self, module): """Register a new module.""" @@ -92,11 +97,8 @@ class I3statusHandler: module.registered(self) def run(self): - jio = JSONIO(self.io) - - while True: - with jio.read() as j: - for module in self.modules: - output = module.output - if output: - j.insert(0, output) + for j in self.io.read(): + for module in self.modules: + output = module.output + if output: + j.insert(0, output) From 143d7d1a40f18e56c3dfd10c2304e4cdf353435f Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 18 Feb 2013 18:03:54 +0100 Subject: [PATCH 3/5] Turns out, i3bar doesn't care for "None". One conditional less <3 --- i3pystatus/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index 3063c6e..7eb0c48 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -99,6 +99,4 @@ class I3statusHandler: def run(self): for j in self.io.read(): for module in self.modules: - output = module.output - if output: - j.insert(0, output) + j.insert(0, module.output) From 6ff3e8ebf34178deedd0feb3bf141c526117c002 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 18 Feb 2013 18:06:08 +0100 Subject: [PATCH 4/5] One may want to change the ordering ("injecting" items in the middle of i3status output, for example) --- i3pystatus/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index 7eb0c48..dac594f 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -9,6 +9,7 @@ from contextlib import contextmanager class Module: output = None + position = 0 def registered(self, status_handler): """Called when this module is registered with a status handler""" @@ -90,13 +91,14 @@ class I3statusHandler: self.io = JSONIO(IOHandler(fd)) - def register(self, module): + def register(self, module, position=0): """Register a new module.""" self.modules.append(module) + module.position = position module.registered(self) def run(self): for j in self.io.read(): for module in self.modules: - j.insert(0, module.output) + j.insert(module.position, module.output) From e3829b5700fd1dbee250811071a1878d323ffeeb Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 18 Feb 2013 19:15:27 +0100 Subject: [PATCH 5/5] We were finally successful in moving the input loop to the lower I/O layer. This means we unified the loop with it's implicit abort condition (end of stream or ^C). --- i3pystatus/__init__.py | 50 ++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index dac594f..485de11 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -39,40 +39,56 @@ class IOHandler: self.inp = inp self.out = out - def write(self, message): + def write_line(self, message): """Unbuffered printing to stdout.""" self.out.write(message + "\n") self.out.flush() def read(self): - """Interrupted respecting reader for stdin.""" + """Iterate over all input lines (Generator)""" + + while True: + try: + yield self.read_line() + except EOFError: + return + + def read_line(self): + """Interrupted respecting reader for stdin. + + Raises EOFError if the end of stream has been reached""" - # try reading a line, removing any extra whitespace try: line = self.inp.readline().decode("utf-8").strip() - # i3status sends EOF, or an empty line - if not line: - sys.exit(3) - return line - # exit on ctrl-c except KeyboardInterrupt: - sys.exit() + raise EOFError() + + # i3status sends EOF, or an empty line + if not line: + raise EOFError() + return line class JSONIO: def __init__(self, io): self.io = io - self.io.write(self.io.read()) - self.io.write(self.io.read()) + self.io.write_line(self.io.read_line()) + self.io.write_line(self.io.read_line()) def read(self): - while True: - with self.read_line() as j: + """Iterate over all JSON input (Generator)""" + + for line in self.io.read(): + with self.parse_line(line) as j: yield j @contextmanager - def read_line(self): - line, prefix = self.io.read(), "" + def parse_line(self, line): + """Parse a single line of JSON and write modified JSON back. + + Usage is quite simple using the usual with-Syntax.""" + + prefix = "" # ignore comma at start of lines if line.startswith(","): @@ -80,7 +96,7 @@ class JSONIO: j = json.loads(line) yield j - self.io.write(prefix + json.dumps(j)) + self.io.write_line(prefix + json.dumps(j)) class I3statusHandler: modules = [] @@ -99,6 +115,6 @@ class I3statusHandler: module.registered(self) def run(self): - for j in self.io.read(): + for j in self.io.read(): for module in self.modules: j.insert(module.position, module.output)