def __init__(self, row_pattern): parts = [p.replace(r"\:", ":") for p in (re.split(r"(?<!\\):", row_pattern))] is_numeric = lambda s: s[:2] == "~#" and "~" not in s[2:] is_pattern = lambda s: '<' in s f_round = lambda s: (isinstance(s, float) and "%.2f" % s) or s def is_date(s): return s in TIME_TAGS disp = parts[1] if len( parts) >= 2 else r"[i][span alpha='40%']<~#tracks>[/span][/i]" cat = parts[0] if is_pattern(cat): title = util.pattern(cat, esc=True, markup=True) try: pc = XMLFromPattern(cat) except ValueError: pc = XMLFromPattern("") tags = pc.tags format = pc.format_list has_markup = True else: title = util.tag(cat) tags = util.tagsplit(cat) has_markup = False if is_date(cat): def format(song: AudioFile) -> List[Tuple[Text, Text]]: fmt = config.gettext("settings", "datecolumn_timestamp_format") date_str = format_date(song(cat), fmt) return [(date_str, date_str)] elif is_numeric(cat): def format(song: AudioFile) -> List[Tuple[Text, Text]]: v = str(f_round(song(cat))) return [(v, v)] else: def format(song: AudioFile) -> List[Tuple[Text, Text]]: return song.list_separate(cat) if is_pattern(disp): try: pd = XMLFromPattern(disp) except ValueError: pd = XMLFromPattern("") format_display = pd.format else: if is_numeric(disp): format_display = lambda coll: str(f_round(coll(disp))) else: format_display = lambda coll: util.escape(coll.comma(disp)) self.title = title self.tags = set(tags) self.format = format self.format_display = format_display self.has_markup = has_markup
def __init__(self, row_pattern): parts = re.split(r"(?<!\\):", row_pattern) parts = list(map(lambda p: p.replace(r"\:", ":"), parts)) is_numeric = lambda s: s[:2] == "~#" and "~" not in s[2:] is_pattern = lambda s: '<' in s f_round = lambda s: (isinstance(s, float) and "%.2f" % s) or s disp = (len(parts) >= 2 and parts[1]) or r"[i](<~#tracks>)[/i]" cat = parts[0] if is_pattern(cat): title = util.pattern(cat, esc=True, markup=True) try: pc = XMLFromPattern(cat) except ValueError: pc = XMLFromPattern("") tags = pc.tags format = pc.format_list has_markup = True else: title = util.tag(cat) tags = util.tagsplit(cat) has_markup = False if is_numeric(cat): def format(song): v = text_type(f_round(song(cat))) return [(v, v)] else: format = lambda song: song.list_separate(cat) if is_pattern(disp): try: pd = XMLFromPattern(disp) except ValueError: pd = XMLFromPattern("") format_display = pd.format else: if is_numeric(disp): format_display = lambda coll: text_type(f_round(coll(disp))) else: format_display = lambda coll: util.escape(coll.comma(disp)) self.title = title self.tags = set(tags) self.format = format self.format_display = format_display self.has_markup = has_markup
def pattern(pat, cap=True, esc=False, markup=False): """Return a 'natural' version of the pattern string for human-readable bits. Assumes all tags in the pattern are present. """ from quodlibet.pattern import Pattern, XMLFromPattern, XMLFromMarkupPattern class Fakesong(dict): cap = False def comma(self, key): return " - ".join(self.list(key)) def list(self, key): return [tag(k, self.cap) for k in tagsplit(key)] list_separate = list def __call__(self, tag, *args): return 0 if '~#' in tag[:2] else self.comma(tag) fakesong = Fakesong({'filename': tag('filename', cap)}) fakesong.cap = cap try: if markup: p = XMLFromMarkupPattern(pat) elif esc: p = XMLFromPattern(pat) else: p = Pattern(pat) except ValueError: return _("Invalid pattern") return p.format(fakesong)
def pattern(pat, cap=True, esc=False): """Return a 'natural' version of the pattern string for human-readable bits. Assumes all tags in the pattern are present.""" from quodlibet.pattern import Pattern, XMLFromPattern class Fakesong(dict): cap = False def comma(self, key): return " - ".join(self.list(key)) def list(self, key): return [tag(k, self.cap) for k in tagsplit(key)] list_seperate = list __call__ = comma fakesong = Fakesong({'filename': tag('filename', cap)}) fakesong.cap = cap try: p = (esc and XMLFromPattern(pat)) or Pattern(pat) except ValueError: return _("Invalid pattern") return p.format(fakesong)
def __check_markup(self, apply): try: f = AudioFile({"~filename": "dummy"}) Pango.parse_markup(XMLFromPattern(self.text) % f, -1, u"\u0000") except (ValueError, GLib.GError), e: qltk.ErrorMessage( self, _("Invalid pattern"), _("The pattern you entered was invalid. Make sure you enter " "< and > as \\< and \\> and that your tags are " "balanced.\n\n%s") % util.escape(str(e))).run() apply.stop_emission('clicked')
def validate_markup_pattern(text, alternative_markup=True, links=False): """Check whether a passed pattern results in a valid pango markup. Args: text (unicode): the pattern alternative_markup (bool): if "[b]" gets mapped to "\\<b\\>" links (bool): if link tags are allowed (for Gtk.Label only) Raises: ValueError: In case the pattern isn't valid """ assert isinstance(text, str) f = AudioFile({"~filename": fsnative(u"dummy")}) try: if alternative_markup: pattern = XMLFromMarkupPattern(text) else: pattern = XMLFromPattern(text) text = pattern % f except PatternError as e: return ValueError(e) try: Pango.parse_markup(text, -1, u"\u0000") except GLib.GError as e: if not links: raise ValueError(e) # Gtk.Label supports links on top of pango markup but doesn't # provide a way to verify them. We can check if the markup # was accepted by seeing if get_text() returns something. l = Gtk.Label() # add a character in case text is empty. # this might print a warning to stderr.. no idea how to prevent that.. l.set_markup(text + " ") if not l.get_text(): raise ValueError(e)
def show_notification(self, song): """Returns True if showing the notification was successful""" if not song: return True try: if self.__enabled: # we are enabled try to work with the data we have and # keep it fresh if not self.__interface: iface, caps, spec = self.__get_interface() self.__interface = iface self.__caps = caps self.__spec_version = spec if "actions" in caps: self.__action_sig = iface.connect( 'g-signal', self._on_signal) else: iface = self.__interface caps = self.__caps spec = self.__spec_version else: # not enabled, just get everything temporary, # probably preview iface, caps, spec = self.__get_interface() except GLib.Error: print_w("[notify] %s" % _("Couldn't connect to notification daemon.")) self.__disconnect() return False strip_markup = lambda t: re.subn(r"\</?[iub]\>", "", t)[0] strip_links = lambda t: re.subn(r"\</?a.*?\>", "", t)[0] strip_images = lambda t: re.subn(r"\<img.*?\>", "", t)[0] title = XMLFromPattern(pconfig.gettext("titlepattern")) % song title = unescape(strip_markup(strip_links(strip_images(title)))) body = "" if "body" in caps: body = XMLFromPattern(pconfig.gettext("bodypattern")) % song if "body-markup" not in caps: body = strip_markup(body) if "body-hyperlinks" not in caps: body = strip_links(body) if "body-images" not in caps: body = strip_images(body) actions = [] if pconfig.getboolean("show_next_button") and "actions" in caps: actions = ["next", _("Next")] hints = { "desktop-entry": GLib.Variant('s', "io.github.quodlibet.QuodLibet"), } image_uri = self._get_image_uri(song) if image_uri: hints["image_path"] = GLib.Variant('s', image_uri) hints["image-path"] = GLib.Variant('s', image_uri) try: self.__last_id = iface.Notify('(susssasa{sv}i)', "Quod Libet", self.__last_id, image_uri, title, body, actions, hints, pconfig.getint("timeout")) except GLib.Error: print_w("[notify] %s" % _("Couldn't connect to notification daemon.")) self.__disconnect() return False # preview done, remove all references again if not self.__enabled: self.__disconnect() return True
def test_cond_markup(s): pat = XMLFromPattern(r'<title|\<b\><title> woo\</b\>>') s.assertEquals(pat.format(s.a), '<b>Title5 woo</b>')
def test_escape(s): pat = XMLFromPattern(r'\<b\><<xmltest>>\</b\>') s.assertEquals(pat.format(s.a), '<b><<&>></b>')
def test_markup_passthrough(s): pat = XMLFromPattern(r'\<b\><<title>>\</b\>') s.assertEquals(pat.format(s.a), '<b><Title5></b>') s.assertEquals(pat.format(s.b), '<b><Title6></b>') s.assertEquals(pat.format(s.c), '<b><test/subdir></b>')
from quodlibet import util from quodlibet import config from quodlibet import _ from quodlibet.pattern import XMLFromPattern from quodlibet.qltk.models import ObjectTreeStore, ObjectModelFilter from quodlibet.qltk.models import ObjectModelSort from quodlibet.compat import iteritems, string_types, itervalues EMPTY = _("Songs not in an album") ALBUM_PATTERN = r""" \<b\><album|<album>|%s>\</b\><date| \<small\>(<date>)\</small\>> \<small\><~discs|<~discs> - ><~tracks> - <~long-length>\</small\>""" % EMPTY ALBUM_PATTERN = ALBUM_PATTERN.lstrip() PAT = XMLFromPattern(ALBUM_PATTERN) UNKNOWN_PATTERN = "<b><i>%s</i></b>" % _("Unknown %s") MULTI_PATTERN = "<b><i>%s</i></b>" % _("Multiple %s Values") COUNT_PATTERN = " <span size='small' color='#777'>(%s)</span>" class AlbumNode(object): def __init__(self, album): self.album = album self.scanned = False @property def COVER_SIZE(self): size = config.getint("browsers", "cover_size")
def show_notification(self, song): """Returns True if showing the notification was successful""" if not song: return True try: if self.__enabled: # we are enabled try to work with the data we have and # keep it fresh if not self.__interface: iface, caps, spec = self.__get_interface() self.__interface = iface self.__caps = caps self.__spec_version = spec if "actions" in caps: self.__action_sig = iface.connect_to_signal( "ActionInvoked", self.on_dbus_action) else: iface = self.__interface caps = self.__caps spec = self.__spec_version else: # not enabled, just get everything temporary, # propably preview iface, caps, spec = self.__get_interface() except dbus.DBusException: print_w("[notify] %s" % _("Couldn't connect to notification daemon.")) self.__disconnect() return False strip_markup = lambda t: re.subn("\</?[iub]\>", "", t)[0] strip_links = lambda t: re.subn("\</?a.*?\>", "", t)[0] strip_images = lambda t: re.subn("\<img.*?\>", "", t)[0] title = XMLFromPattern(get_conf_value("titlepattern")) % song title = unescape(strip_markup(strip_links(strip_images(title)))) body = "" if "body" in caps: body = XMLFromPattern(get_conf_value("bodypattern")) % song if "body-markup" not in caps: body = strip_markup(body) if "body-hyperlinks" not in caps: body = strip_links(body) if "body-images" not in caps: body = strip_images(body) actions = [] if get_conf_bool("show_next_button") and "actions" in caps: actions = ["next", _("Next")] hints = { "desktop-entry": "quodlibet", } image_uri = self._get_image_uri(song) if image_uri: hints["image_path"] = image_uri hints["image-path"] = image_uri try: self.__last_id = iface.Notify("Quod Libet", self.__last_id, image_uri, title, body, actions, hints, get_conf_int("timeout")) except dbus.DBusException: print_w("[notify] %s" % _("Couldn't connect to notification daemon.")) self.__disconnect() return False # preview done, remove all references again if not self.__enabled: self.__disconnect() return True