@ -1,6 +1,72 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Content plugin management.
Content that can be included in a songbook is controlled by plugins . From the
user ( or . sb file ) point of view , each piece of content is introduced by a
keyword . This keywold is associated with a plugin ( a submodule of this very
module ) , which parses the content , and return a list of instances of the
Content class .
# Plugin definition
A plugin is a submodule of this module , which have a variable
CONTENT_PLUGINS , which is a dictionary where :
- keys are keywords ,
- values are parsers ( see below ) .
When analysing the content field of the . sb file , when those keywords are
met , the corresponding parser is called .
# Parsers
A parser is a function which takes as arguments :
- keyword : the keyword triggering this function ;
- argument : the argument of the keyword ( see below ) ;
- contentlist : the list of content , that is , the part of the list
following the keyword ( see example below ) ;
- config : the configuration object of the current songbook . Plugins can
change it .
A parser returns a list of instances of the Content class , defined in
this module ( or of subclasses of this class ) .
Example : When the following piece of content is met
[ " sorted(author, @title) " , " a_song.sg " , " another_song.sg " ]
the parser associated to keyword ' sorted ' get the arguments :
- keyword = " sorted "
- argument = " author, @title "
- contentlist = [ " a_song.sg " , " another_song.sg " ]
- config = < the config file of the current songbook > .
# Keyword
A keyword is either an identifier ( alphanumeric characters , and underscore ) ,
or such an identifier , with some text surrounded by parenthesis ( like a
function definition ) ; this text is called the argument to the keyword .
Examples :
- sorted
- sorted ( author , @title )
- cwd ( some / path )
If the keyword has an argument , it can be anything , given that it is
surrounded by parenthesis . It is up to the plugin to parse this argument . For
intance , keyword " foo()(( bar() " is a perfectly valid keyword , and the parser
associated to " foo " will get as argument the string " )(( bar( " .
# Content class
The content classes are subclasses of class Content defined in this module .
Content is a perfectly valid class , but instances of it will not generate
anything in the resulting . tex .
More documentation in the docstring of Content .
"""
import glob
import importlib
import jinja2
@ -13,10 +79,15 @@ from songbook_core.errors import SongbookError
LOGGER = logging . getLogger ( __name__ )
EOL = ' \n '
class Content :
""" Content item of type ' example ' . """
#pylint: disable=no-self-use
class Content ( object ) :
""" Content item. Will render to something in the .tex file.
The current jinja2 . runtime . Context is passed to all function defined
here .
"""
def render ( self , context ) :
def render ( self , __ context) :
""" Render this content item.
Returns a string , to be placed verbatim in the generated . tex file .
@ -25,31 +96,35 @@ class Content:
# Block management
def begin_new_block ( self , previous , context ) :
def begin_new_block ( self , __ previous, __ context) :
""" Return a boolean stating if a new block is to be created.
# Arguments
- previous : the songbook . content . Content object of the previous item .
- context : current jinja2 . runtime . Context .
- __ previous: the songbook . content . Content object of the previous item .
- __ context: see Content ( ) documentation .
# Return
True if the renderer has to close previous block , and begin a new one ,
False otherwise .
- True if the renderer has to close previous block , and begin a new
one ,
- False otherwise ( the generated code for this item is part of the
current block ) .
"""
return True
def begin_block ( self , context ) :
def begin_block ( self , __ context) :
""" Return the string to begin a block. """
return " "
def end_block ( self , context ) :
def end_block ( self , __ context) :
""" Return the string to end a block. """
return " "
class ContentError ( SongbookError ) :
""" Error in a content plugin. """
def __init__ ( self , keyword , message ) :
super ( ContentError , self ) . __init__ ( )
self . keyword = keyword
self . message = message
@ -67,17 +142,31 @@ def load_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 ' ) ] ) )
)
' 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 )
LOGGER . warning (
" File %s : Keyword ' %s ' is already used. Ignored. " ,
os . path . relpath ( name ) ,
key ,
)
continue
plugins [ key ] = value
return plugins
@jinja2 . contextfunction
def render_content ( context , content ) :
""" Render the content of the songbook as a LaTeX code.
Arguments :
- context : the jinja2 . runtime . context of the current template
compilation .
- content : a list of Content ( ) instances , as the one that was returned by
process_content ( ) .
"""
rendered = " "
previous = None
last = None
@ -99,7 +188,17 @@ def render_content(context, content):
return rendered
def process_content ( content , config = None ) :
def process_content ( content , config = None ) :
""" Process content, and return a list of Content() objects.
Arguments are :
- content : the content field of the . sb file , which should be a list , and
describe what is to be included in the songbook ;
- config : the configuration dictionary of the current songbook .
Return : a list of Content objects , corresponding to the content to be
included in the . tex file .
"""
contentlist = [ ]
plugins = load_plugins ( )
keyword_re = re . compile ( r ' ^ *(?P<keyword> \ w*) *( \ ((?P<argument>.*) \ ))? *$ ' )
@ -119,8 +218,8 @@ def process_content(content, config = None):
raise ContentError ( keyword , " Unknown content type. " )
contentlist . extend ( plugins [ keyword ] (
keyword ,
argument = argument ,
contentlist = elem [ 1 : ] ,
config = config ,
argument = argument ,
contentlist = elem [ 1 : ] ,
config = config ,
) )
return contentlist