3.24: Introduce time wrapper, remove battery remaining_* formatter(!!!)

This commit is contained in:
enkore 2013-08-04 23:40:19 +02:00
parent 3394aa56ea
commit 69b702d2f1
7 changed files with 120 additions and 35 deletions

View File

@ -19,6 +19,18 @@ status output compatible to i3status / i3bar of the i3 window manager.
* [Arch Linux](https://aur.archlinux.org/packages/i3pystatus-git/) * [Arch Linux](https://aur.archlinux.org/packages/i3pystatus-git/)
### Release Notes
#### 3.24
**This release introduced changes that may require manual changes to your
configuration file**
* Introduced TimeWrapper
* battery module: removed remaining_\* formatters in favor of TimeWrapper,
as it can not only reproduce all the variants removed, but can do much more.
* mpd: Uses TimeWrapper for song_length, song_elapsed
## Configuration ## Configuration
You can keep your config file at various places, i3pystatus will look You can keep your config file at various places, i3pystatus will look
@ -75,7 +87,7 @@ from network, wireless and pulseaudio in this example):
# This would also display a desktop notification (via dbus) if the percentage # This would also display a desktop notification (via dbus) if the percentage
# goes below 5 percent while discharging. The block will also color RED. # goes below 5 percent while discharging. The block will also color RED.
status.register("battery", status.register("battery",
format="{status}/{consumption:.2f}W {percentage:.2f}% [{percentage_design:.2f}%] {remaining_hm}", format="{status}/{consumption:.2f}W {percentage:.2f}% [{percentage_design:.2f}%] {remaining:%E%hh:%Mm}",
alert=True, alert=True,
alert_percentage=5, alert_percentage=5,
status={ status={
@ -185,6 +197,27 @@ Inside a group always all format specifiers must evaluate to true (logical and).
You can nest groups. The inner group will only become part of the output if both You can nest groups. The inner group will only become part of the output if both
the outer group and the inner group are eligible for output. the outer group and the inner group are eligible for output.
#### TimeWrapper
Some modules that output times use TimeWrapper to format these. TimeWrapper is
a mere extension of the standard formatting method.
The time format that should be used is specified using the format specifier, i.e.
with some_time being 3951 seconds a format string like `{some_time:%h:%m:%s}`
would produce `1:5:51`
* `%h`, `%m` and `%s` are the hours, minutes and seconds without leading zeros
(i.e. 0 to 59 for minutes and seconds)
* `%H`, `%M` and `%S` are padded with a leading zero to two digits, i.e. 00 to 59
* `%l` and `%L` produce hours non-padded and padded but only if hours is not zero.
If the hours are zero it produces an empty string.
* `%%` produces a literal %
* `%E` (only valid on beginning of the string) if the time is null, don't format
anything but rather produce an empty string. If the time is non-null it is
removed from the string.
* When the module in question also uses formatp, 0 seconds counts as "not known".
* The formatted time is stripped, i.e. spaces on both ends of the result are removed
## Modules ## Modules
@ -244,8 +277,7 @@ battery status
Available formatters for format and alert_format_\*: Available formatters for format and alert_format_\*:
* `{remaining_str}` — remaining time for charging or discharging in the format H:MM * `{remaining}` — remaining time for charging or discharging, uses TimeWrapper formatting, default format is `%E%h:%M`
* `{remaining_hm}`- remaining time in the format Hh:MMm
* `{percentage}` — battery percentage relative to the last full value * `{percentage}` — battery percentage relative to the last full value
* `{percentage_design}` — absolute battery charge percentage * `{percentage_design}` — absolute battery charge percentage
* `{consumption (Watts)}` — current power flowing into/out of the battery * `{consumption (Watts)}` — current power flowing into/out of the battery
@ -256,13 +288,13 @@ Available formatters for format and alert_format_\*:
__Settings:__ __Settings:__
* `battery_ident` — The name of your battery, usually BAT0 or BAT1 (default: `BAT0`) * `battery_ident` — The name of your battery, usually BAT0 or BAT1 (default: `BAT0`)
* `format` — (default: `{status} {remaining_hm}`) * `format` — (default: `{status} {remaining}`)
* `alert` — Display a libnotify-notification on low battery (default: `False`) * `alert` — Display a libnotify-notification on low battery (default: `False`)
* `alert_percentage` — (default: `10`) * `alert_percentage` — (default: `10`)
* `alert_format_title` — The title of the notification, all formatters can be used (default: `Low battery`) * `alert_format_title` — The title of the notification, all formatters can be used (default: `Low battery`)
* `alert_format_body` — The body text of the notification, all formatters can be used (default: `Battery {battery_ident} has only {percentage:.2f}% ({remaining_hm}) remaining!`) * `alert_format_body` — The body text of the notification, all formatters can be used (default: `Battery {battery_ident} has only {percentage:.2f}% ({remaining_hm}) remaining!`)
* `path` — Override the default-generated path (default: `None`) * `path` — Override the default-generated path (default: `None`)
* `status` — A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names (default: `{'FULL': 'FULL', 'DIS': 'DIS', 'CHR': 'CHR'}`) * `status` — A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names (default: `{'CHR': 'CHR', 'DIS': 'DIS', 'FULL': 'FULL'}`)
@ -431,8 +463,8 @@ Available formatters (uses formatp)
* `{title}` — (the title of the current song) * `{title}` — (the title of the current song)
* `{album}` — (the album of the current song, can be an empty string (e.g. for online streams)) * `{album}` — (the album of the current song, can be an empty string (e.g. for online streams))
* `{artist}` — (can be empty, too) * `{artist}` — (can be empty, too)
* `{song_elapsed}` — (Position in the currently playing song, looks like 3:54) * `{song_elapsed}` — (Position in the currently playing song, **uses TimeWrapper**, default is `%m:%S`)
* `{song_length}` — (Length of the current song, same format as song_elapsed) * `{song_length}` — (Length of the current song, same as song_elapsed)
* `{pos}` — (Position of current song in playlist, one-based) * `{pos}` — (Position of current song in playlist, one-based)
* `{len}` — (Songs in playlist) * `{len}` — (Songs in playlist)
* `{status}` — (play, pause, stop mapped through the `status` dictionary) * `{status}` — (play, pause, stop mapped through the `status` dictionary)
@ -447,7 +479,7 @@ __Settings:__
* `host` — (default: `localhost`) * `host` — (default: `localhost`)
* `port` — MPD port (default: `6600`) * `port` — MPD port (default: `6600`)
* `format` — formatp string (default: `{title} {status}`) * `format` — formatp string (default: `{title} {status}`)
* `status` — Dictionary mapping pause, play and stop to output (default: `{'stop': '◾', 'pause': '▷', 'play': '▶'}`) * `status` — Dictionary mapping pause, play and stop to output (default: `{'play': '▶', 'stop': '◾', 'pause': '▷'}`)
@ -503,6 +535,7 @@ Shows volume of default PulseAudio sink (output).
Available formatters: Available formatters:
* `{volume}` — volume in percent (0...100) * `{volume}` — volume in percent (0...100)
* `{db}` — volume in decibels relative to 100 %, i.e. 100 % = 0 dB, 50 % = -18 dB, 0 % = -infinity dB * `{db}` — volume in decibels relative to 100 %, i.e. 100 % = 0 dB, 50 % = -18 dB, 0 % = -infinity dB
(the literal value for -infinity is `-∞`)
__Settings:__ __Settings:__

View File

@ -19,6 +19,18 @@ status output compatible to i3status / i3bar of the i3 window manager.
* [Arch Linux](https://aur.archlinux.org/packages/i3pystatus-git/) * [Arch Linux](https://aur.archlinux.org/packages/i3pystatus-git/)
### Release Notes
#### 3.24
**This release introduced changes that may require manual changes to your
configuration file**
* Introduced TimeWrapper
* battery module: removed remaining_\* formatters in favor of TimeWrapper,
as it can not only reproduce all the variants removed, but can do much more.
* mpd: Uses TimeWrapper for song_length, song_elapsed
## Configuration ## Configuration
You can keep your config file at various places, i3pystatus will look You can keep your config file at various places, i3pystatus will look
@ -75,7 +87,7 @@ from network, wireless and pulseaudio in this example):
# This would also display a desktop notification (via dbus) if the percentage # This would also display a desktop notification (via dbus) if the percentage
# goes below 5 percent while discharging. The block will also color RED. # goes below 5 percent while discharging. The block will also color RED.
status.register("battery", status.register("battery",
format="{status}/{consumption:.2f}W {percentage:.2f}% [{percentage_design:.2f}%] {remaining_hm}", format="{status}/{consumption:.2f}W {percentage:.2f}% [{percentage_design:.2f}%] {remaining:%E%hh:%Mm}",
alert=True, alert=True,
alert_percentage=5, alert_percentage=5,
status={ status={
@ -185,6 +197,27 @@ Inside a group always all format specifiers must evaluate to true (logical and).
You can nest groups. The inner group will only become part of the output if both You can nest groups. The inner group will only become part of the output if both
the outer group and the inner group are eligible for output. the outer group and the inner group are eligible for output.
#### TimeWrapper
Some modules that output times use TimeWrapper to format these. TimeWrapper is
a mere extension of the standard formatting method.
The time format that should be used is specified using the format specifier, i.e.
with some_time being 3951 seconds a format string like `{some_time:%h:%m:%s}`
would produce `1:5:51`
* `%h`, `%m` and `%s` are the hours, minutes and seconds without leading zeros
(i.e. 0 to 59 for minutes and seconds)
* `%H`, `%M` and `%S` are padded with a leading zero to two digits, i.e. 00 to 59
* `%l` and `%L` produce hours non-padded and padded but only if hours is not zero.
If the hours are zero it produces an empty string.
* `%%` produces a literal %
* `%E` (only valid on beginning of the string) if the time is null, don't format
anything but rather produce an empty string. If the time is non-null it is
removed from the string.
* When the module in question also uses formatp, 0 seconds counts as "not known".
* The formatted time is stripped, i.e. spaces on both ends of the result are removed
## Modules ## Modules
!!module_doc!! !!module_doc!!

View File

@ -4,9 +4,9 @@
import re import re
import configparser import configparser
from . import IntervalModule from i3pystatus import IntervalModule
from .core.util import PrefixedKeyDict, lchop from i3pystatus.core.util import PrefixedKeyDict, lchop, TimeWrapper
from .core.desktop import display_notification from i3pystatus.core.desktop import display_notification
class UEventParser(configparser.ConfigParser): class UEventParser(configparser.ConfigParser):
@staticmethod @staticmethod
@ -78,18 +78,6 @@ class BatteryEnergy(Battery):
else: else:
return (self.bat["ENERGY_FULL"] - self.bat["ENERGY_NOW"]) / self.bat["POWER_NOW"] * 60 return (self.bat["ENERGY_FULL"] - self.bat["ENERGY_NOW"]) / self.bat["POWER_NOW"] * 60
def format_remaining(minutes, prefix):
hours, minutes = map(int, divmod(minutes, 60))
d = PrefixedKeyDict(prefix)
d.update({
"str": "{}:{:02}".format(hours, minutes),
"hm": "{}h:{:02}m".format(hours, minutes),
"hours": hours,
"mins": minutes,
})
return d
class BatteryChecker(IntervalModule): class BatteryChecker(IntervalModule):
""" """
This class uses the /sys/class/power_supply//uevent interface to check for the This class uses the /sys/class/power_supply//uevent interface to check for the
@ -97,8 +85,7 @@ class BatteryChecker(IntervalModule):
Available formatters for format and alert_format_\*: Available formatters for format and alert_format_\*:
* `{remaining_str}` remaining time for charging or discharging in the format H:MM * `{remaining}` remaining time for charging or discharging, uses TimeWrapper formatting, default format is `%E%h:%M`
* `{remaining_hm}`- remaining time in the format Hh:MMm
* `{percentage}` battery percentage relative to the last full value * `{percentage}` battery percentage relative to the last full value
* `{percentage_design}` absolute battery charge percentage * `{percentage_design}` absolute battery charge percentage
* `{consumption (Watts)}` current power flowing into/out of the battery * `{consumption (Watts)}` current power flowing into/out of the battery
@ -117,7 +104,7 @@ class BatteryChecker(IntervalModule):
("status", "A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names"), ("status", "A dictionary mapping ('DIS', 'CHR', 'FULL') to alternative names"),
) )
battery_ident = "BAT0" battery_ident = "BAT0"
format = "{status} {remaining_hm}" format = "{status} {remaining}"
status = { status = {
"CHR": "CHR", "CHR": "CHR",
"DIS": "DIS", "DIS": "DIS",
@ -143,17 +130,16 @@ class BatteryChecker(IntervalModule):
fdict = { fdict = {
"battery_ident": self.battery_ident, "battery_ident": self.battery_ident,
"remaining_str": "",
"remaining_hm": "",
"percentage": battery.percentage(), "percentage": battery.percentage(),
"percentage_design": battery.percentage(design=True), "percentage_design": battery.percentage(design=True),
"consumption": battery.consumption(), "consumption": battery.consumption(),
"remaining": TimeWrapper(0, "%E%h:%M"),
} }
status = battery.status() status = battery.status()
if status in ["Discharging", "Charging"]: if status in ["Discharging", "Charging"]:
remaining = battery.remaining() remaining = battery.remaining()
fdict.update(format_remaining(remaining, "remaining_")) fdict["remaining"] = TimeWrapper(remaining * 60, "%E%h:%M")
if status == "Discharging": if status == "Discharging":
fdict["status"] = "DIS" fdict["status"] = "DIS"
if remaining < 15: if remaining < 15:

View File

@ -2,6 +2,7 @@
import collections import collections
import itertools import itertools
import re import re
import string
from i3pystatus.core.exceptions import * from i3pystatus.core.exceptions import *
from i3pystatus.core.imputil import ClassFinder from i3pystatus.core.imputil import ClassFinder
@ -240,3 +241,32 @@ def formatp(string, **kwargs):
return merge_tree(tree) return merge_tree(tree)
formatp.field_re = re.compile(r"({(\w+)[^}]*})") formatp.field_re = re.compile(r"({(\w+)[^}]*})")
class TimeWrapper:
class TimeTemplate(string.Template):
delimiter = "%"
idpattern = r"[a-zA-Z]"
def __init__(self, seconds, default_format="%m:%S"):
self.seconds = int(seconds)
self.default_format = default_format
def __bool__(self):
return bool(self.seconds)
def __format__(self, format_spec):
format_spec = format_spec or self.default_format
h = self.seconds // 3600
m, s = divmod(self.seconds % 3600, 60)
l = h if h else ""
L = "%02d" % h if h else ""
if format_spec.startswith("%E"):
format_spec = format_spec[2:]
if not self.seconds:
return ""
return self.TimeTemplate(format_spec).substitute(
h=h, m=m, s=s,
H="%02d" % h, M="%02d" % m, S="%02d" % s,
l=l, L=L,
).strip()

View File

@ -2,6 +2,7 @@
import socket import socket
from i3pystatus import IntervalModule, formatp from i3pystatus import IntervalModule, formatp
from i3pystatus.core.util import TimeWrapper
def format_time(seconds): def format_time(seconds):
return "{}:{:02}".format(*divmod(int(seconds), 60)) if seconds else "" return "{}:{:02}".format(*divmod(int(seconds), 60)) if seconds else ""
@ -14,8 +15,8 @@ class MPD(IntervalModule):
* `{title}` (the title of the current song) * `{title}` (the title of the current song)
* `{album}` (the album of the current song, can be an empty string (e.g. for online streams)) * `{album}` (the album of the current song, can be an empty string (e.g. for online streams))
* `{artist}` (can be empty, too) * `{artist}` (can be empty, too)
* `{song_elapsed}` (Position in the currently playing song, looks like 3:54) * `{song_elapsed}` (Position in the currently playing song, **uses TimeWrapper**, default is `%m:%S`)
* `{song_length}` (Length of the current song, same format as song_elapsed) * `{song_length}` (Length of the current song, same as song_elapsed)
* `{pos}` (Position of current song in playlist, one-based) * `{pos}` (Position of current song in playlist, one-based)
* `{len}` (Songs in playlist) * `{len}` (Songs in playlist)
* `{status}` (play, pause, stop mapped through the `status` dictionary) * `{status}` (play, pause, stop mapped through the `status` dictionary)
@ -78,8 +79,8 @@ class MPD(IntervalModule):
"title": currentsong.get("Title", ""), "title": currentsong.get("Title", ""),
"album": currentsong.get("Album", ""), "album": currentsong.get("Album", ""),
"artist": currentsong.get("Artist", ""), "artist": currentsong.get("Artist", ""),
"song_length": format_time(currentsong.get("Time", 0)), "song_length": TimeWrapper(currentsong.get("Time", 0)),
"song_elapsed": format_time(float(status.get("elapsed", 0))), "song_elapsed": TimeWrapper(float(status.get("elapsed", 0))),
"bitrate": int(status.get("bitrate", 0)), "bitrate": int(status.get("bitrate", 0)),
} }

View File

@ -3,7 +3,7 @@
from setuptools import setup from setuptools import setup
setup(name="i3pystatus", setup(name="i3pystatus",
version="3.23", version="3.24",
description="Like i3status, this generates status line for i3bar / i3wm", description="Like i3status, this generates status line for i3bar / i3wm",
url="http://github.com/enkore/i3pystatus", url="http://github.com/enkore/i3pystatus",
license="MIT", license="MIT",

View File

@ -266,6 +266,8 @@ class FormatPTests(unittest.TestCase):
def test_side_by_side(self): def test_side_by_side(self):
s = "{status} [{artist} / [{album} / ]]{title}[ {song_elapsed}/{song_length}]" s = "{status} [{artist} / [{album} / ]]{title}[ {song_elapsed}/{song_length}]"
assert util.formatp(s, status="", title="Only For The Weak", song_elapsed="1:41", song_length="4:55") == "▷ Only For The Weak 1:41/4:55" assert util.formatp(s, status="", title="Only For The Weak", song_elapsed="1:41", song_length="4:55") == "▷ Only For The Weak 1:41/4:55"
assert util.formatp(s, status="", album="Foo", title="Die, Die, Crucified", song_elapsed="2:52") == " Die, Die, Crucified"
assert util.formatp("[[{a}][{b}]]", b=1) == "1"
def test_complex_field(self): def test_complex_field(self):
class NS: class NS: