mirror of https://github.com/patacrep/patacrep.git
Luthaf
11 years ago
17 changed files with 755 additions and 250 deletions
@ -0,0 +1,226 @@ |
|||||
|
#!/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 |
||||
|
import logging |
||||
|
import os |
||||
|
import re |
||||
|
|
||||
|
from songbook_core import files |
||||
|
from songbook_core.errors import SongbookError |
||||
|
|
||||
|
LOGGER = logging.getLogger(__name__) |
||||
|
EOL = '\n' |
||||
|
|
||||
|
#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): |
||||
|
"""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, __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: see Content() documentation. |
||||
|
|
||||
|
# Return |
||||
|
|
||||
|
- 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): |
||||
|
"""Return the string to begin a block.""" |
||||
|
return "" |
||||
|
|
||||
|
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 |
||||
|
|
||||
|
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.", |
||||
|
files.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 |
||||
|
for elem in content: |
||||
|
if not isinstance(elem, Content): |
||||
|
LOGGER.error("Ignoring bad content item '{}'.".format(elem)) |
||||
|
continue |
||||
|
|
||||
|
last = elem |
||||
|
if elem.begin_new_block(previous, context): |
||||
|
if previous: |
||||
|
rendered += previous.end_block(context) + EOL |
||||
|
rendered += elem.begin_block(context) + EOL |
||||
|
rendered += elem.render(context) + EOL |
||||
|
previous = elem |
||||
|
|
||||
|
if isinstance(last, Content): |
||||
|
rendered += last.end_block(context) + EOL |
||||
|
|
||||
|
return rendered |
||||
|
|
||||
|
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>.*)\))? *$') |
||||
|
if not content: |
||||
|
content = [["song"]] |
||||
|
for elem in content: |
||||
|
if isinstance(elem, basestring): |
||||
|
elem = ["song", elem] |
||||
|
if len(content) == 0: |
||||
|
content = ["song"] |
||||
|
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 |
@ -0,0 +1,39 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
"""Change base directory before importing songs.""" |
||||
|
|
||||
|
import os |
||||
|
|
||||
|
from songbook_core.content import process_content |
||||
|
|
||||
|
#pylint: disable=unused-argument |
||||
|
def parse(keyword, config, argument, contentlist): |
||||
|
"""Return a list songs included in contentlist, whith a different base path. |
||||
|
|
||||
|
Arguments: |
||||
|
- keyword: unused; |
||||
|
- config: the current songbook configuration dictionary; |
||||
|
- argument: a directory; |
||||
|
- contentlist: songbook content, that is parsed by |
||||
|
songbook_core.content.process_content(). |
||||
|
|
||||
|
This function adds 'argument' to the directories where songs are searched |
||||
|
for, and then processes the content. |
||||
|
|
||||
|
The 'argument' is added: |
||||
|
- first as a relative path to the current directory; |
||||
|
- then as a relative path to every path already present in |
||||
|
config['songdir']. |
||||
|
""" |
||||
|
old_songdir = config['_songdir'] |
||||
|
config['_songdir'] = ( |
||||
|
[argument] + |
||||
|
[os.path.join(path, argument) for path in config['_songdir']] + |
||||
|
config['_songdir'] |
||||
|
) |
||||
|
processed_content = process_content(contentlist, config) |
||||
|
config['_songdir'] = old_songdir |
||||
|
return processed_content |
||||
|
|
||||
|
CONTENT_PLUGINS = {'cwd': parse} |
@ -0,0 +1,60 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
"""Allow LaTeX sections (starred or not) as content of a songbook.""" |
||||
|
|
||||
|
from songbook_core.content import Content, ContentError |
||||
|
|
||||
|
KEYWORDS = [ |
||||
|
"part", |
||||
|
"chapter", |
||||
|
"section", |
||||
|
"subsection", |
||||
|
"subsubsection", |
||||
|
"paragraph", |
||||
|
"subparagraph", |
||||
|
] |
||||
|
FULL_KEYWORDS = KEYWORDS + ["{}*".format(word) for word in KEYWORDS] |
||||
|
|
||||
|
class Section(Content): |
||||
|
"""A LaTeX section.""" |
||||
|
|
||||
|
def __init__(self, keyword, name, short=None): |
||||
|
self.keyword = keyword |
||||
|
self.name = name |
||||
|
self.short = short |
||||
|
|
||||
|
def render(self, __context): |
||||
|
if self.short is None: |
||||
|
return r'\{}{{{}}}'.format(self.keyword, self.name) |
||||
|
else: |
||||
|
return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) |
||||
|
|
||||
|
#pylint: disable=unused-argument |
||||
|
def parse(keyword, argument, contentlist, config): |
||||
|
"""Parse the contentlist. |
||||
|
|
||||
|
Arguments: |
||||
|
- keyword (one of "part", "chapter", "section", ... , "subparagraph", and |
||||
|
their starred versions "part*", "chapter*", ... , "subparagraph*"): the |
||||
|
section to use; |
||||
|
- argument: unused; |
||||
|
- contentlist: a list of one or two strings, which are the names (short |
||||
|
and long) of the section; |
||||
|
- config: configuration dictionary of the current songbook. |
||||
|
""" |
||||
|
if (keyword not in KEYWORDS) and (len(contentlist) != 1): |
||||
|
raise ContentError( |
||||
|
keyword, |
||||
|
"Starred section names must have exactly one argument." |
||||
|
) |
||||
|
if (len(contentlist) not in [1, 2]): |
||||
|
raise ContentError(keyword, "Section can have one or two arguments.") |
||||
|
return [Section(keyword, *contentlist)] #pylint: disable=star-args |
||||
|
|
||||
|
|
||||
|
CONTENT_PLUGINS = dict([ |
||||
|
(word, parse) |
||||
|
for word |
||||
|
in FULL_KEYWORDS |
||||
|
]) |
@ -0,0 +1,114 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
"""Plugin to include songs to the songbook.""" |
||||
|
|
||||
|
import glob |
||||
|
import jinja2 |
||||
|
import logging |
||||
|
import os |
||||
|
|
||||
|
from songbook_core.content import Content, process_content, ContentError |
||||
|
from songbook_core import files |
||||
|
from songbook_core.songs import Song |
||||
|
|
||||
|
LOGGER = logging.getLogger(__name__) |
||||
|
|
||||
|
class SongRenderer(Content, Song): |
||||
|
"""Render a song in the .tex file.""" |
||||
|
|
||||
|
def begin_new_block(self, previous, __context): |
||||
|
"""Return a boolean stating if a new block is to be created.""" |
||||
|
return not isinstance(previous, SongRenderer) |
||||
|
|
||||
|
def begin_block(self, context): |
||||
|
"""Return the string to begin a block.""" |
||||
|
indexes = context.resolve("indexes") |
||||
|
if isinstance(indexes, jinja2.runtime.Undefined): |
||||
|
indexes = "" |
||||
|
return r'\begin{songs}{%s}' % indexes |
||||
|
|
||||
|
def end_block(self, __context): |
||||
|
"""Return the string to end a block.""" |
||||
|
return r'\end{songs}' |
||||
|
|
||||
|
def render(self, context): |
||||
|
"""Return the string that will render the song.""" |
||||
|
return r'\input{{{}}}'.format(files.relpath( |
||||
|
self.path, |
||||
|
os.path.dirname(context['filename']) |
||||
|
)) |
||||
|
|
||||
|
#pylint: disable=unused-argument |
||||
|
def parse(keyword, argument, contentlist, config): |
||||
|
"""Parse data associated with keyword 'song'. |
||||
|
|
||||
|
Arguments: |
||||
|
- keyword: unused; |
||||
|
- argument: unused; |
||||
|
- contentlist: a list of strings, which are interpreted as regular |
||||
|
expressions (interpreted using the glob module), referring to songs. |
||||
|
- config: the current songbook configuration dictionary. |
||||
|
|
||||
|
Return a list of SongRenderer() instances. |
||||
|
""" |
||||
|
if 'languages' not in config: |
||||
|
config['_languages'] = set() |
||||
|
songlist = [] |
||||
|
for songdir in config['_songdir']: |
||||
|
if contentlist: |
||||
|
break |
||||
|
contentlist = [ |
||||
|
files.relpath(filename, songdir) |
||||
|
for filename |
||||
|
in files.recursive_find(songdir, "*.sg") |
||||
|
] |
||||
|
for elem in contentlist: |
||||
|
before = len(songlist) |
||||
|
for songdir in config['_songdir']: |
||||
|
for filename in glob.iglob(os.path.join(songdir, elem)): |
||||
|
LOGGER.debug('Parsing file "{}"…'.format(filename)) |
||||
|
song = SongRenderer(filename, config) |
||||
|
songlist.append(song) |
||||
|
config["_languages"].update(song.languages) |
||||
|
if len(songlist) > before: |
||||
|
break |
||||
|
if len(songlist) == before: |
||||
|
# No songs were added |
||||
|
LOGGER.warning( |
||||
|
"Expression '{}' did not match any file".format(elem) |
||||
|
) |
||||
|
return songlist |
||||
|
|
||||
|
|
||||
|
CONTENT_PLUGINS = {'song': parse} |
||||
|
|
||||
|
|
||||
|
class OnlySongsError(ContentError): |
||||
|
"A list that should contain only songs also contain other type of content." |
||||
|
def __init__(self, not_songs): |
||||
|
super(OnlySongsError, self).__init__() |
||||
|
self.not_songs = not_songs |
||||
|
|
||||
|
def __str__(self): |
||||
|
return ( |
||||
|
"Only songs are allowed, and the following items are not:" + |
||||
|
str(self.not_songs) |
||||
|
) |
||||
|
|
||||
|
def process_songs(content, config=None): |
||||
|
"""Process content that containt only songs. |
||||
|
|
||||
|
Call songbook_core.content.process_content(), checks if the returned list |
||||
|
contains only songs, and raise an exception if not. |
||||
|
""" |
||||
|
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 |
@ -0,0 +1,46 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
"""Allow 'songchapter' and 'songsection' as content of a songbook.""" |
||||
|
|
||||
|
from songbook_core.content import Content, ContentError |
||||
|
|
||||
|
KEYWORDS = [ |
||||
|
"songchapter", |
||||
|
"songsection", |
||||
|
] |
||||
|
|
||||
|
class SongSection(Content): |
||||
|
"""A songsection or songchapter.""" |
||||
|
|
||||
|
def __init__(self, keyword, name): |
||||
|
self.keyword = keyword |
||||
|
self.name = name |
||||
|
|
||||
|
def render(self, __context): |
||||
|
"""Render this section or chapter.""" |
||||
|
return r'\{}{{{}}}'.format(self.keyword, self.name) |
||||
|
|
||||
|
#pylint: disable=unused-argument |
||||
|
def parse(keyword, argument, contentlist, config): |
||||
|
"""Parse the contentlist. |
||||
|
|
||||
|
Arguments: |
||||
|
- keyword ("songsection" or "songchapter"): the section to use; |
||||
|
- argument: unused; |
||||
|
- contentlist: a list of one string, which is the name of the section; |
||||
|
- config: configuration dictionary of the current songbook. |
||||
|
""" |
||||
|
if (keyword not in KEYWORDS) and (len(contentlist) != 1): |
||||
|
raise ContentError( |
||||
|
keyword, |
||||
|
"Starred section names must have exactly one argument.", |
||||
|
) |
||||
|
return [SongSection(keyword, contentlist[0])] |
||||
|
|
||||
|
|
||||
|
CONTENT_PLUGINS = dict([ |
||||
|
(word, parse) |
||||
|
for word |
||||
|
in KEYWORDS |
||||
|
]) |
@ -0,0 +1,96 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
"""Sorted list of songs. |
||||
|
|
||||
|
This plugin provides keyword 'sorted', used to include a sorted list of songs |
||||
|
to a songbook. |
||||
|
""" |
||||
|
|
||||
|
import locale |
||||
|
import logging |
||||
|
|
||||
|
from songbook_core import files |
||||
|
from songbook_core.content import ContentError |
||||
|
from songbook_core.content.song import OnlySongsError, process_songs |
||||
|
|
||||
|
LOGGER = logging.getLogger(__name__) |
||||
|
|
||||
|
DEFAULT_SORT = ['by', 'album', '@title'] |
||||
|
|
||||
|
def normalize_string(string): |
||||
|
"""Return a normalized string. |
||||
|
|
||||
|
Normalized means: |
||||
|
- no surrounding spaces; |
||||
|
- lower case; |
||||
|
- passed through locale.strxfrm(). |
||||
|
""" |
||||
|
return locale.strxfrm(string.lower().strip()) |
||||
|
|
||||
|
def normalize_field(field): |
||||
|
"""Return a normalized field, it being a string or a list of strings.""" |
||||
|
if isinstance(field, basestring): |
||||
|
return normalize_string(field) |
||||
|
elif isinstance(field, list): |
||||
|
return [normalize_string(string) for string in field] |
||||
|
|
||||
|
def key_generator(sort): |
||||
|
"""Return a function that returns the list of values used to sort the song. |
||||
|
|
||||
|
Arguments: |
||||
|
- sort: the list of keys used to sort. |
||||
|
""" |
||||
|
|
||||
|
def ordered_song_keys(song): |
||||
|
"""Return the list of values used to sort the song.""" |
||||
|
songkey = [] |
||||
|
for key in sort: |
||||
|
if key == "@title": |
||||
|
field = song.unprefixed_titles |
||||
|
elif key == "@path": |
||||
|
field = song.path |
||||
|
elif key == "by": |
||||
|
field = song.authors |
||||
|
else: |
||||
|
try: |
||||
|
field = song.args[key] |
||||
|
except KeyError: |
||||
|
LOGGER.debug( |
||||
|
"Ignoring unknown key '{}' for song {}.".format( |
||||
|
key, |
||||
|
files.relpath(song.path), |
||||
|
) |
||||
|
) |
||||
|
field = "" |
||||
|
songkey.append(normalize_field(field)) |
||||
|
return songkey |
||||
|
return ordered_song_keys |
||||
|
|
||||
|
#pylint: disable=unused-argument |
||||
|
def parse(keyword, config, argument, contentlist): |
||||
|
"""Return a sorted list of songs contained in 'contentlist'. |
||||
|
|
||||
|
Arguments: |
||||
|
- keyword: the string 'sorted'; |
||||
|
- config: the current songbook configuration dictionary; |
||||
|
- argument: the list of the fields used to sort songs, as strings |
||||
|
separated by commas (e.g. "by, album, @title"); |
||||
|
- contentlist: the list of content to be sorted. If this content |
||||
|
contain something else than a song, an exception is raised. |
||||
|
""" |
||||
|
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 be 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} |
@ -0,0 +1,58 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
"""Include LaTeX raw code in the songbook.""" |
||||
|
|
||||
|
import logging |
||||
|
import os |
||||
|
|
||||
|
from songbook_core import files |
||||
|
from songbook_core.content import Content |
||||
|
|
||||
|
LOGGER = logging.getLogger(__name__) |
||||
|
|
||||
|
class LaTeX(Content): |
||||
|
"""Inclusion of LaTeX code""" |
||||
|
|
||||
|
def __init__(self, filename): |
||||
|
self.filename = filename |
||||
|
|
||||
|
def render(self, context): |
||||
|
return r'\input{{{}}}'.format(files.relpath( |
||||
|
self.filename, |
||||
|
os.path.dirname(context['filename']), |
||||
|
)) |
||||
|
|
||||
|
#pylint: disable=unused-argument |
||||
|
def parse(keyword, argument, contentlist, config): |
||||
|
"""Parse the contentlist. |
||||
|
|
||||
|
Arguments: |
||||
|
- keyword: unused; |
||||
|
- argument: unused; |
||||
|
- contentlist: a list of name of tex files; |
||||
|
- config: configuration dictionary of the current songbook. |
||||
|
""" |
||||
|
if not contentlist: |
||||
|
LOGGER.warning( |
||||
|
"Useless 'tex' content: list of files to include is empty." |
||||
|
) |
||||
|
filelist = [] |
||||
|
for filename in contentlist: |
||||
|
checked_file = None |
||||
|
for path in config['_songdir']: |
||||
|
if os.path.exists(os.path.join(path, filename)): |
||||
|
checked_file = os.path.relpath(os.path.join(path, filename)) |
||||
|
break |
||||
|
if not checked_file: |
||||
|
LOGGER.warning( |
||||
|
("Cannot find file '{}' in '{}'. Compilation may fail " |
||||
|
"later.").format(filename, str(config['_songdir'])) |
||||
|
) |
||||
|
continue |
||||
|
filelist.append(LaTeX(checked_file)) |
||||
|
|
||||
|
return filelist |
||||
|
|
||||
|
|
||||
|
CONTENT_PLUGINS = {'tex': parse} |
Loading…
Reference in new issue