diff --git a/songbook_core/content/__init__.py b/songbook_core/content/__init__.py index e69de29b..68323ee8 100644 --- a/songbook_core/content/__init__.py +++ b/songbook_core/content/__init__.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import glob +import importlib +import logging +import os + +from songbook_core.errors import SongbookError + +LOGGER = logging.getLogger(__name__) + +class Content: + """Content item of type 'example'.""" + + def render(self): + """Render this content item. + + Returns a string, to be placed verbatim in the generated .tex file. + """ + return "" + + # Block management + + def begin_new_block(self, previous): + """Return a boolean stating if a new block is to be created. + + # Arguments + + - previous: the songbook.content.Content object of the previous item. + + # Return + + True if the renderer has to close previous block, and begin a new one, + False otherwise. + + # Default + + The default behavior of this method (if not defined in this child + class) is: begin a new block if the previous item is not an instance of + the same class. + """ + return False + + def begin_block(self): + """Return the string to begin a block.""" + return "" + + def end_block(self): + """Return the string to end a block.""" + return "" + +class ContentError(SongbookError): + def __init__(self, keyword, message): + self.keyword = keyword + self.message = message + + def __str__(self): + return "Content: {}: {}".format(self.keyword, self.message) + +def load_plugins(): + """Load all content plugins, and return a dictionary of those plugins. + + Return value: a dictionary where: + - keys are the keywords ; + - values are functions triggered when this keyword is met. + """ + plugins = {} + for name in glob.glob(os.path.join(os.path.dirname(__file__), "*.py")): + if name.endswith(".py") and os.path.basename(name) != "__init__.py": + plugin = importlib.import_module( + 'songbook_core.content.{}'.format(os.path.basename(name[:-len('.py')])) + ) + for (key, value) in plugin.CONTENT_PLUGINS.items(): + if key in plugins: + LOGGER.warning("File %s: Keyword '%s' is already used. Ignored.", os.path.relpath(name), key) + continue + plugins[key] = value + return plugins diff --git a/songbook_core/content/example b/songbook_core/content/example deleted file mode 100644 index 061fe599..00000000 --- a/songbook_core/content/example +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Example of a plugin managing a content type. - -TODO: Explain""" - -import songbook.content - -class Example(songbook.content.Content): - """Content item of type 'example'.""" - - def render(self, TODO): - """Render this content item. - - Returns a string, to be placed verbatim (? TODO) in the generated .tex - file. - """ - return "" - - # Block management - - def begin_new_block(self, previous): - """Return a boolean stating if a new block is to be created. - - # Arguments - - - previous: the songbook.content.Content object of the previous item. - - # Return - - True if the renderer has to close previous block, and begin a new one, - False otherwise. - - # Default - - The default behavior of this method (if not defined in this child - class) is: begin a new block if the previous item is not an instance of - the same class. - """ - return False - - def begin_block(self, TODO): - """Return the string to begin a block.""" - return "" - - def end_block(self, TODO): - """Return the string to end a block.""" - return "" - - -def parse(keyword, arguments): - """Parse songbook .sb content item. - - Takes as argument the keyword triggerring this content, and the list of - arguments, e.g. for a content item '["picture", "*.png", "*.jpg"]', the - call of this function is: - - > parse('picture', ["*.png", "*.jpg"]) - - Return a list of (subclasses of) songbook.content.Content objects. - """ - return Example(keyword, arguments) - -songbook.content.register('example', parse) diff --git a/songbook_core/content/section.py b/songbook_core/content/section.py new file mode 100644 index 00000000..99f66ef7 --- /dev/null +++ b/songbook_core/content/section.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from songbook_core.content import Content + +KEYWORDS = [ + "part", + "chapter", + "section", + "subsection", + "subsubsection", + "paragraph", + "subparagraph", + ] +FULL_KEYWORDS = KEYWORDS + [ "{}*".format(keyword) for keyword in KEYWORDS] + +class Section(Content): + def __init__(self, keyword, name, short = None): + self.keyword = keyword + self.name = name + self.short = short + + def render(self): + if (short is None): + return r'\{}{{{}}}'.format(self.keyword, self.name) + else: + return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) + +def parse(keyword, arguments): + if (keyword not in KEYWORDS) and (len(arguments) != 1): + raise ContentError(keyword, "Starred section names must have exactly one argument.") + if (len(arguments) not in [1, 2]): + raise ContentError(keyword, "Section can have one or two arguments.") + return [Section(keyword, *arguments)] + + +CONTENT_PLUGINS = dict([(keyword, parse) for keyword in FULL_KEYWORDS]) diff --git a/songbook_core/content/songsection.py b/songbook_core/content/songsection.py new file mode 100644 index 00000000..0b246e65 --- /dev/null +++ b/songbook_core/content/songsection.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from songbook_core.content import Content + +KEYWORDS = [ + "songchapter", + "songsection", + ] + +class SongSection(Content): + def __init__(self, keyword): + self.keyword = keyword + self.name = name + + def render(self): + return r'\{}{{{}}}'.format(self.keyword, self.name) + +def parse(keyword, arguments): + if (keyword not in KEYWORDS) and (len(arguments) != 1): + raise ContentError(keyword, "Starred section names must have exactly one argument.") + return [SongSection(keyword, *arguments)] + + +CONTENT_PLUGINS = dict([(keyword, parse) for keyword in KEYWORDS])