diff --git a/songbook_core/content/__init__.py b/songbook_core/content/__init__.py index b5aed178..2c3498a6 100644 --- a/songbook_core/content/__init__.py +++ b/songbook_core/content/__init__.py @@ -6,6 +6,7 @@ import importlib import jinja2 import logging import os +import re from songbook_core.errors import SongbookError @@ -101,6 +102,7 @@ def render_content(context, content): def process_content(content, config = None): contentlist = [] plugins = load_plugins() + keyword_re = re.compile(r'^ *(?P\w*) *(\((?P.*)\))? *$') if not content: content = [["song"]] for elem in content: @@ -108,7 +110,17 @@ def process_content(content, config = None): elem = ["song", elem] if len(content) == 0: content = ["song"] - if elem[0] not in plugins: - raise ContentError(elem[0], "Unknown content type.") - contentlist.extend(plugins[elem[0]](elem[0], config, *elem[1:])) + try: + match = keyword_re.match(elem[0]).groupdict() + except AttributeError: + raise ContentError(elem[0], "Cannot parse content type.") + (keyword, argument) = (match['keyword'], match['argument']) + if keyword not in plugins: + raise ContentError(keyword, "Unknown content type.") + contentlist.extend(plugins[keyword]( + keyword, + argument = argument, + contentlist = elem[1:], + config = config, + )) return contentlist diff --git a/songbook_core/content/section.py b/songbook_core/content/section.py index f2cf4e89..74f9e31e 100644 --- a/songbook_core/content/section.py +++ b/songbook_core/content/section.py @@ -26,12 +26,12 @@ class Section(Content): else: return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) -def parse(keyword, config, *arguments): - if (keyword not in KEYWORDS) and (len(arguments) != 1): +def parse(keyword, argument, contentlist, config): + if (keyword not in KEYWORDS) and (len(contentlist) != 1): raise ContentError(keyword, "Starred section names must have exactly one argument.") - if (len(arguments) not in [1, 2]): + if (len(contentlist) not in [1, 2]): raise ContentError(keyword, "Section can have one or two arguments.") - return [Section(keyword, *arguments)] + return [Section(keyword, *contentlist)] CONTENT_PLUGINS = dict([(keyword, parse) for keyword in FULL_KEYWORDS]) diff --git a/songbook_core/content/song.py b/songbook_core/content/song.py index 27aca0e8..1aeaf15e 100644 --- a/songbook_core/content/song.py +++ b/songbook_core/content/song.py @@ -6,7 +6,7 @@ import jinja2 import logging import os -from songbook_core.content import Content +from songbook_core.content import Content, process_content, ContentError from songbook_core.files import recursive_find from songbook_core.songs import Song @@ -33,12 +33,12 @@ class SongRenderer(Content, Song): path = os.path.abspath(self.path) return r'\input{{{}}}'.format(path) -def parse(keyword, config, *arguments): +def parse(keyword, argument, contentlist, config): if 'languages' not in config: config['languages'] = set() songlist = [] - if not arguments: - arguments = [ + if not contentlist: + contentlist = [ os.path.relpath( filename, os.path.join(config['datadir'][0], 'songs'), @@ -49,7 +49,7 @@ def parse(keyword, config, *arguments): "*.sg" ) ] - for elem in arguments: + for elem in contentlist: before = len(songlist) for songdir in [os.path.join(d, 'songs') for d in config['datadir']]: for filename in glob.iglob(os.path.join(songdir, elem)): @@ -68,3 +68,18 @@ def parse(keyword, config, *arguments): CONTENT_PLUGINS = {'song': parse} + + +class OnlySongsError(ContentError): + def __init__(self, not_songs): + self.not_songs = not_songs + + def __str__(self): + return "Only songs are allowed, and the following items are not:" + str(not_songs) + +def process_songs(content, config = None): + contentlist = process_content(content, config) + not_songs = [item for item in contentlist if not isinstance(item, SongRenderer)] + if not_songs: + raise OnlySongsError(not_songs) + return contentlist diff --git a/songbook_core/content/songsection.py b/songbook_core/content/songsection.py index c27a3a31..1d1abd14 100644 --- a/songbook_core/content/songsection.py +++ b/songbook_core/content/songsection.py @@ -16,10 +16,10 @@ class SongSection(Content): def render(self, __context): return r'\{}{{{}}}'.format(self.keyword, self.name) -def parse(keyword, config, *arguments): - if (keyword not in KEYWORDS) and (len(arguments) != 1): +def parse(keyword, argument, contentlist, config): + if (keyword not in KEYWORDS) and (len(contentlist) != 1): raise ContentError(keyword, "Starred section names must have exactly one argument.") - return [SongSection(keyword, *arguments)] + return [SongSection(keyword, *contentlist)] CONTENT_PLUGINS = dict([(keyword, parse) for keyword in KEYWORDS]) diff --git a/songbook_core/content/sorted.TODO b/songbook_core/content/sorted.TODO deleted file mode 100644 index 2fd4679f..00000000 --- a/songbook_core/content/sorted.TODO +++ /dev/null @@ -1,23 +0,0 @@ - def __cmp__(self, other): - if not isinstance(other, Song): - return NotImplemented - for key in self.sort: - if key == "@title": - self_key = self.normalized_titles - other_key = other.normalized_titles - elif key == "@path": - self_key = locale.strxfrm(self.path) - other_key = locale.strxfrm(other.path) - elif key == "by": - self_key = self.normalized_authors - other_key = other.normalized_authors - else: - self_key = locale.strxfrm(self.args.get(key, "")) - other_key = locale.strxfrm(other.args.get(key, "")) - - if self_key < other_key: - return -1 - elif self_key > other_key: - return 1 - return 0 - diff --git a/songbook_core/content/sorted.py b/songbook_core/content/sorted.py new file mode 100644 index 00000000..6bd1c665 --- /dev/null +++ b/songbook_core/content/sorted.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import locale + +from songbook_core.content.song import OnlySongsError, process_songs + +DEFAULT_SORT = ['by', 'album', '@title'] + +def key_generator(sort): + def ordered_song_keys(song): + songkey = [] + for key in sort: + if key == "@title": + songkey.append(song.normalized_titles) + elif key == "@path": + songkey.append(locale.strxfrm(song.path)) + elif key == "by": + songkey.append(song.normalized_authors) + else: + songkey.append(locale.strxfrm(song.args.get(key, ""))) + return songkey + return ordered_song_keys + +def parse(keyword, config, argument, contentlist): + if argument: + sort = [key.strip() for key in argument.split(",")] + else: + sort = DEFAULT_SORT + try: + songlist = process_songs(contentlist, config) + except OnlySongsError as error: + raise ContentError(keyword, "Content list of this keyword can bo only songs (or content that result into songs), and the following are not:" + str(error.not_songs)) + return sorted(songlist, key=key_generator(sort)) + +CONTENT_PLUGINS = {'sorted': parse}