Browse Source

The code is pylint compliant.

pull/47/head
Louis 11 years ago
parent
commit
d066386812
  1. 7
      songbook_core/authors.py
  2. 24
      songbook_core/build.py
  3. 131
      songbook_core/content/__init__.py
  4. 20
      songbook_core/content/cwd.py
  5. 37
      songbook_core/content/section.py
  6. 41
      songbook_core/content/song.py
  7. 29
      songbook_core/content/songsection.py
  8. 39
      songbook_core/content/sorted.py
  9. 5
      songbook_core/index.py
  10. 1
      songbook_core/plastex.py
  11. 6
      songbook_core/songs.py
  12. 4
      songbook_core/templates.py

7
songbook_core/authors.py

@ -12,8 +12,11 @@ DEFAULT_AUTHWORDS = {
} }
def compile_authwords(authwords): def compile_authwords(authwords):
# Convert strings to regular expressions """Convert strings of authwords to compiled regular expressions.
# Fill holes
This regexp will later be used to match these words in authors strings.
"""
# Fill missing values
for (key, value) in DEFAULT_AUTHWORDS.items(): for (key, value) in DEFAULT_AUTHWORDS.items():
if key not in authwords: if key not in authwords:
authwords[key] = value authwords[key] = value

24
songbook_core/build.py

@ -7,7 +7,6 @@ import codecs
import glob import glob
import logging import logging
import os.path import os.path
import re
from subprocess import Popen, PIPE, call from subprocess import Popen, PIPE, call
from songbook_core import __DATADIR__ from songbook_core import __DATADIR__
@ -15,7 +14,6 @@ from songbook_core import authors
from songbook_core import content from songbook_core import content
from songbook_core import errors from songbook_core import errors
from songbook_core.index import process_sxd from songbook_core.index import process_sxd
from songbook_core.songs import Song
from songbook_core.templates import TexRenderer from songbook_core.templates import TexRenderer
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -54,6 +52,7 @@ class Songbook(object):
super(Songbook, self).__init__() super(Songbook, self).__init__()
self.config = raw_songbook self.config = raw_songbook
self.basename = basename self.basename = basename
self.contentlist = []
# Some special keys have their value processed. # Some special keys have their value processed.
self._set_datadir() self._set_datadir()
@ -70,14 +69,26 @@ class Songbook(object):
if os.path.exists(path) and os.path.isdir(path): if os.path.exists(path) and os.path.isdir(path):
abs_datadir.append(os.path.abspath(path)) abs_datadir.append(os.path.abspath(path))
else: else:
LOGGER.warning("Ignoring non-existent datadir '{}'.".format(path)) LOGGER.warning(
"Ignoring non-existent datadir '{}'.".format(path)
)
abs_datadir.append(__DATADIR__) abs_datadir.append(__DATADIR__)
self.config['datadir'] = abs_datadir self.config['datadir'] = abs_datadir
self.config['_songdir'] = [os.path.join(path, 'songs') for path in self.config['datadir']] self.config['_songdir'] = [
os.path.join(path, 'songs')
for path in self.config['datadir']
]
def build_config(self, from_templates): def build_config(self, from_templates):
"""Build configuration dictionary
This dictionary is assembled using (by order of least precedence):
- the hard-coded default;
- the values read from templates;
- the values read from .sb file.
"""
config = DEFAULT_CONFIG config = DEFAULT_CONFIG
config.update(from_templates) config.update(from_templates)
config.update(self.config) config.update(self.config)
@ -100,7 +111,10 @@ class Songbook(object):
) )
context = self.build_config(renderer.get_variables()) context = self.build_config(renderer.get_variables())
self.contentlist = content.process_content(self.config.get('content', []), context) self.contentlist = content.process_content(
self.config.get('content', []),
context,
)
context['render_content'] = content.render_content context['render_content'] = content.render_content
context['titleprefixkeys'] = ["after", "sep", "ignore"] context['titleprefixkeys'] = ["after", "sep", "ignore"]
context['content'] = self.contentlist context['content'] = self.contentlist

131
songbook_core/content/__init__.py

@ -1,6 +1,72 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- 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 glob
import importlib import importlib
import jinja2 import jinja2
@ -13,10 +79,15 @@ from songbook_core.errors import SongbookError
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
EOL = '\n' EOL = '\n'
class Content: #pylint: disable=no-self-use
"""Content item of type 'example'.""" class Content(object):
"""Content item. Will render to something in the .tex file.
def render(self, context): The current jinja2.runtime.Context is passed to all function defined
here.
"""
def render(self, __context):
"""Render this content item. """Render this content item.
Returns a string, to be placed verbatim in the generated .tex file. Returns a string, to be placed verbatim in the generated .tex file.
@ -25,31 +96,35 @@ class Content:
# Block management # 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. """Return a boolean stating if a new block is to be created.
# Arguments # Arguments
- previous: the songbook.content.Content object of the previous item. - __previous: the songbook.content.Content object of the previous item.
- context: current jinja2.runtime.Context. - __context: see Content() documentation.
# Return # Return
True if the renderer has to close previous block, and begin a new one, - True if the renderer has to close previous block, and begin a new
False otherwise. one,
- False otherwise (the generated code for this item is part of the
current block).
""" """
return True return True
def begin_block(self, context): def begin_block(self, __context):
"""Return the string to begin a block.""" """Return the string to begin a block."""
return "" return ""
def end_block(self, context): def end_block(self, __context):
"""Return the string to end a block.""" """Return the string to end a block."""
return "" return ""
class ContentError(SongbookError): class ContentError(SongbookError):
"""Error in a content plugin."""
def __init__(self, keyword, message): def __init__(self, keyword, message):
super(ContentError, self).__init__()
self.keyword = keyword self.keyword = keyword
self.message = message self.message = message
@ -67,17 +142,31 @@ def load_plugins():
for name in glob.glob(os.path.join(os.path.dirname(__file__), "*.py")): for name in glob.glob(os.path.join(os.path.dirname(__file__), "*.py")):
if name.endswith(".py") and os.path.basename(name) != "__init__.py": if name.endswith(".py") and os.path.basename(name) != "__init__.py":
plugin = importlib.import_module( 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(): for (key, value) in plugin.CONTENT_PLUGINS.items():
if key in plugins: 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 continue
plugins[key] = value plugins[key] = value
return plugins return plugins
@jinja2.contextfunction @jinja2.contextfunction
def render_content(context, content): 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 = "" rendered = ""
previous = None previous = None
last = None last = None
@ -99,7 +188,17 @@ def render_content(context, content):
return rendered 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 = [] contentlist = []
plugins = load_plugins() plugins = load_plugins()
keyword_re = re.compile(r'^ *(?P<keyword>\w*) *(\((?P<argument>.*)\))? *$') 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.") raise ContentError(keyword, "Unknown content type.")
contentlist.extend(plugins[keyword]( contentlist.extend(plugins[keyword](
keyword, keyword,
argument = argument, argument=argument,
contentlist = elem[1:], contentlist=elem[1:],
config = config, config=config,
)) ))
return contentlist return contentlist

20
songbook_core/content/cwd.py

@ -1,11 +1,31 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Change base directory before importing songs."""
import os import os
from songbook_core.content import process_content from songbook_core.content import process_content
#pylint: disable=unused-argument
def parse(keyword, config, argument, contentlist): 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'].
"""
config['_songdir'] = ( config['_songdir'] = (
[os.path.relpath(argument)] + [os.path.relpath(argument)] +
[os.path.join(path, argument) for path in config['_songdir']] + [os.path.join(path, argument) for path in config['_songdir']] +

37
songbook_core/content/section.py

@ -1,7 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from songbook_core.content import Content """Allow LaTeX sections (starred or not) as content of a songbook."""
from songbook_core.content import Content, ContentError
KEYWORDS = [ KEYWORDS = [
"part", "part",
@ -12,26 +14,47 @@ KEYWORDS = [
"paragraph", "paragraph",
"subparagraph", "subparagraph",
] ]
FULL_KEYWORDS = KEYWORDS + [ "{}*".format(keyword) for keyword in KEYWORDS] FULL_KEYWORDS = KEYWORDS + ["{}*".format(word) for word in KEYWORDS]
class Section(Content): class Section(Content):
def __init__(self, keyword, name, short = None): """A LaTeX section."""
def __init__(self, keyword, name, short=None):
self.keyword = keyword self.keyword = keyword
self.name = name self.name = name
self.short = short self.short = short
def render(self, __context): def render(self, __context):
if (self.short is None): if self.short is None:
return r'\{}{{{}}}'.format(self.keyword, self.name) return r'\{}{{{}}}'.format(self.keyword, self.name)
else: else:
return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name)
#pylint: disable=unused-argument
def parse(keyword, argument, contentlist, config): 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): if (keyword not in KEYWORDS) and (len(contentlist) != 1):
raise ContentError(keyword, "Starred section names must have exactly one argument.") raise ContentError(
keyword,
"Starred section names must have exactly one argument."
)
if (len(contentlist) not in [1, 2]): if (len(contentlist) not in [1, 2]):
raise ContentError(keyword, "Section can have one or two arguments.") raise ContentError(keyword, "Section can have one or two arguments.")
return [Section(keyword, *contentlist)] return [Section(keyword, *contentlist)] #pylint: disable=star-args
CONTENT_PLUGINS = dict([(keyword, parse) for keyword in FULL_KEYWORDS]) CONTENT_PLUGINS = dict([
(word, parse)
for word
in FULL_KEYWORDS
])

41
songbook_core/content/song.py

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Plugin to include songs to the songbook."""
import glob import glob
import jinja2 import jinja2
import logging import logging
@ -13,19 +15,25 @@ from songbook_core.songs import Song
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
class SongRenderer(Content, Song): class SongRenderer(Content, Song):
"""Render a song in the .tex file."""
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."""
return not isinstance(previous, SongRenderer) return not isinstance(previous, SongRenderer)
def begin_block(self, context): def begin_block(self, context):
"""Return the string to begin a block."""
indexes = context.resolve("indexes") indexes = context.resolve("indexes")
if isinstance(indexes, jinja2.runtime.Undefined): if isinstance(indexes, jinja2.runtime.Undefined):
indexes = "" indexes = ""
return r'\begin{songs}{%s}' % indexes return r'\begin{songs}{%s}' % indexes
def end_block(self, __context): def end_block(self, __context):
"""Return the string to end a block."""
return r'\end{songs}' return r'\end{songs}'
def render(self, context): def render(self, context):
"""Return the string that will render the song."""
outdir = os.path.dirname(context['filename']) outdir = os.path.dirname(context['filename'])
if os.path.abspath(self.path).startswith(os.path.abspath(outdir)): if os.path.abspath(self.path).startswith(os.path.abspath(outdir)):
path = os.path.relpath(self.path, outdir) path = os.path.relpath(self.path, outdir)
@ -33,7 +41,19 @@ class SongRenderer(Content, Song):
path = os.path.abspath(self.path) path = os.path.abspath(self.path)
return r'\input{{{}}}'.format(path) return r'\input{{{}}}'.format(path)
#pylint: disable=unused-argument
def parse(keyword, argument, contentlist, config): 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: if 'languages' not in config:
config['_languages'] = set() config['_languages'] = set()
songlist = [] songlist = []
@ -67,15 +87,30 @@ CONTENT_PLUGINS = {'song': parse}
class OnlySongsError(ContentError): class OnlySongsError(ContentError):
"A list that should contain only songs also contain other type of content."
def __init__(self, not_songs): def __init__(self, not_songs):
super(OnlySongsError, self).__init__()
self.not_songs = not_songs self.not_songs = not_songs
def __str__(self): def __str__(self):
return "Only songs are allowed, and the following items are not:" + str(not_songs) 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.
def process_songs(content, config = None): 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) contentlist = process_content(content, config)
not_songs = [item for item in contentlist if not isinstance(item, SongRenderer)] not_songs = [
item
for item
in contentlist
if not isinstance(item, SongRenderer)
]
if not_songs: if not_songs:
raise OnlySongsError(not_songs) raise OnlySongsError(not_songs)
return contentlist return contentlist

29
songbook_core/content/songsection.py

@ -1,7 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from songbook_core.content import Content """Allow 'songchapter' and 'songsection' as content of a songbook."""
from songbook_core.content import Content, ContentError
KEYWORDS = [ KEYWORDS = [
"songchapter", "songchapter",
@ -9,17 +11,36 @@ KEYWORDS = [
] ]
class SongSection(Content): class SongSection(Content):
"""A songsection or songchapter."""
def __init__(self, keyword, name): def __init__(self, keyword, name):
self.keyword = keyword self.keyword = keyword
self.name = name self.name = name
def render(self, __context): def render(self, __context):
"""Render this section or chapter."""
return r'\{}{{{}}}'.format(self.keyword, self.name) return r'\{}{{{}}}'.format(self.keyword, self.name)
#pylint: disable=unused-argument
def parse(keyword, argument, contentlist, config): 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): if (keyword not in KEYWORDS) and (len(contentlist) != 1):
raise ContentError(keyword, "Starred section names must have exactly one argument.") raise ContentError(
return [SongSection(keyword, *contentlist)] keyword,
"Starred section names must have exactly one argument.",
)
return [SongSection(keyword, contentlist[0])]
CONTENT_PLUGINS = dict([(keyword, parse) for keyword in KEYWORDS]) CONTENT_PLUGINS = dict([
(word, parse)
for word
in KEYWORDS
])

39
songbook_core/content/sorted.py

@ -1,23 +1,45 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- 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 locale
from songbook_core.content import ContentError
from songbook_core.content.song import OnlySongsError, process_songs from songbook_core.content.song import OnlySongsError, process_songs
DEFAULT_SORT = ['by', 'album', '@title'] DEFAULT_SORT = ['by', 'album', '@title']
def normalize_string(string): 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()) return locale.strxfrm(string.lower().strip())
def normalize_field(field): def normalize_field(field):
"""Return a normalized field, it being a string or a list of strings."""
if isinstance(field, basestring): if isinstance(field, basestring):
return normalize_string(field) return normalize_string(field)
elif isinstance(field, list): elif isinstance(field, list):
return [normalize_string(string) for string in field] return [normalize_string(string) for string in field]
def key_generator(sort): 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): def ordered_song_keys(song):
"""Return the list of values used to sort the song."""
songkey = [] songkey = []
for key in sort: for key in sort:
if key == "@title": if key == "@title":
@ -32,7 +54,18 @@ def key_generator(sort):
return songkey return songkey
return ordered_song_keys return ordered_song_keys
#pylint: disable=unused-argument
def parse(keyword, config, argument, contentlist): 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: if argument:
sort = [key.strip() for key in argument.split(",")] sort = [key.strip() for key in argument.split(",")]
else: else:
@ -40,7 +73,11 @@ def parse(keyword, config, argument, contentlist):
try: try:
songlist = process_songs(contentlist, config) songlist = process_songs(contentlist, config)
except OnlySongsError as error: 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)) 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)) return sorted(songlist, key=key_generator(sort))
CONTENT_PLUGINS = {'sorted': parse} CONTENT_PLUGINS = {'sorted': parse}

5
songbook_core/index.py

@ -30,7 +30,9 @@ def sortkey(value):
don't forget to call locale.setlocale(locale.LC_ALL, '')). It also handles don't forget to call locale.setlocale(locale.LC_ALL, '')). It also handles
the sort with latex escape sequences. the sort with latex escape sequences.
""" """
return locale.strxfrm(unidecode(simpleparse(value).replace(' ', 'A')).lower()) return locale.strxfrm(
unidecode(simpleparse(value).replace(' ', 'A')).lower()
)
def process_sxd(filename): def process_sxd(filename):
@ -65,6 +67,7 @@ class Index(object):
def __init__(self, indextype): def __init__(self, indextype):
self.data = dict() self.data = dict()
self.keywords = dict() self.keywords = dict()
self.authwords = dict()
self.prefix_patterns = [] self.prefix_patterns = []
if indextype == "TITLE INDEX DATA FILE": if indextype == "TITLE INDEX DATA FILE":
self.indextype = "TITLE" self.indextype = "TITLE"

1
songbook_core/plastex.py

@ -13,6 +13,7 @@ import sys
def process_unbr_spaces(node): def process_unbr_spaces(node):
#pylint: disable=line-too-long
r"""Replace '~' and '\ ' in node by nodes that r"""Replace '~' and '\ ' in node by nodes that
will be rendered as unbreakable space. will be rendered as unbreakable space.

6
songbook_core/songs.py

@ -4,7 +4,6 @@
"""Song management.""" """Song management."""
from unidecode import unidecode from unidecode import unidecode
import locale
import re import re
from songbook_core.authors import processauthors from songbook_core.authors import processauthors
@ -30,7 +29,10 @@ class Song(object):
self.path = filename self.path = filename
self.languages = data['languages'] self.languages = data['languages']
if "by" in self.args.keys(): if "by" in self.args.keys():
self.authors = processauthors(self.args["by"], **config["authwords"]) self.authors = processauthors(
self.args["by"],
**config["authwords"]
)
else: else:
self.authors = [] self.authors = []

4
songbook_core/templates.py

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Template for .tex generation settings and utilities""" """Template for .tex generation settings and utilities"""
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PackageLoader, \ from jinja2 import Environment, FileSystemLoader, ChoiceLoader, \
TemplateNotFound, nodes TemplateNotFound, nodes
from jinja2.ext import Extension from jinja2.ext import Extension
from jinja2.meta import find_referenced_templates as find_templates from jinja2.meta import find_referenced_templates as find_templates
@ -54,7 +54,7 @@ class VariablesExtension(Extension):
end_tokens=['name:endvariables'], end_tokens=['name:endvariables'],
drop_needle=True, drop_needle=True,
) )
return nodes.Const("") return nodes.Const("") # pylint: disable=no-value-for-parameter
def _escape_tex(value): def _escape_tex(value):

Loading…
Cancel
Save