3.24: Introduce time wrapper, remove battery remaining_* formatter(!!!)
This commit is contained in:
parent
3394aa56ea
commit
69b702d2f1
49
README.md
49
README.md
@ -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:__
|
||||||
|
@ -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!!
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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)),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
2
setup.py
2
setup.py
@ -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",
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user