Browse Source

Merge pull request #232 from patacrep/sb_get_content

Retrieve processed songbook content
pull/240/head
oliverpool 8 years ago
committed by GitHub
parent
commit
0b92fe1682
  1. 3
      NEWS.md
  2. 48
      patacrep/build.py
  3. 4
      patacrep/content/__init__.py
  4. 6
      patacrep/content/section.py
  5. 3
      patacrep/content/setcounter.py
  6. 3
      patacrep/content/song.py
  7. 3
      patacrep/content/songsection.py
  8. 3
      patacrep/content/tex.py
  9. 2
      patacrep/songs/__init__.py
  10. 13
      patacrep/tools/__init__.py
  11. 17
      patacrep/tools/cache/__main__.py
  12. 0
      patacrep/tools/content/__init__.py
  13. 70
      patacrep/tools/content/__main__.py
  14. 2
      test/test_content/custom.control
  15. 2
      test/test_content/custom.source
  16. 2
      test/test_content/customzipped.control
  17. 2
      test/test_content/customzipped.source
  18. 2
      test/test_content/cwd.control
  19. 4
      test/test_content/cwd_list.control
  20. 4
      test/test_content/datadir/python/content/customplugin.py
  21. BIN
      test/test_content/datadir_zippedcontent/python/content
  22. 2
      test/test_content/glob.control
  23. 12
      test/test_content/include.control
  24. 24
      test/test_content/sections.control
  25. 16
      test/test_content/setcounter.control
  26. 8
      test/test_content/songs.control
  27. 12
      test/test_content/songsection.control
  28. 54
      test/test_content/sort.control
  29. 40
      test/test_content/test_content.py
  30. 6
      test/test_content/tex.control

3
NEWS.md

@ -19,6 +19,9 @@
* LaTeX songs * LaTeX songs
* The `meta` directive is now supported: `\metacrep{COMMANDNAME}{arg}` [#220](https://github.com/patacrep/patacrep/pull/220) * The `meta` directive is now supported: `\metacrep{COMMANDNAME}{arg}` [#220](https://github.com/patacrep/patacrep/pull/220)
* Faster index generation [#233](https://github.com/patacrep/patacrep/pull/233) * Faster index generation [#233](https://github.com/patacrep/patacrep/pull/233)
* Patatools
* New command to generate the list of the content items (songs, sections...): `patatools content items <songbook>` [#232](https://github.com/patacrep/patacrep/pull/232)
# patacrep 5.0.0 # patacrep 5.0.0

48
patacrep/build.py

@ -53,6 +53,19 @@ class Songbook:
self._errors = list() self._errors = list()
self._config = dict() self._config = dict()
def get_content_items(self):
"""Return: a list of ContentItem objects, corresponding to the content to be
included in the .tex file.
"""
content_config = self._raw_config.copy()
# Updates the '_langs' key
content_items = content.process_content(
content_config.get('content', []),
content_config,
)
content_langs = content_config['_langs']
return content_langs, content_items
def write_tex(self, output): def write_tex(self, output):
"""Build the '.tex' file corresponding to self. """Build the '.tex' file corresponding to self.
@ -60,44 +73,41 @@ class Songbook:
- output: a file object, in which the file will be written. - output: a file object, in which the file will be written.
""" """
# Updating configuration # Updating configuration
self._config = self._raw_config.copy() tex_config = self._raw_config.copy()
renderer = TexBookRenderer( renderer = TexBookRenderer(
self._config['book']['template'], tex_config['book']['template'],
self._config['_datadir'], tex_config['_datadir'],
self._config['book']['lang'], tex_config['book']['lang'],
self._config['book']['encoding'], tex_config['book']['encoding'],
) )
try: try:
self._config['_template'] = renderer.get_all_variables(self._config.get('template', {})) tex_config['_template'] = renderer.get_all_variables(tex_config.get('template', {}))
except errors.SchemaError as exception: except errors.SchemaError as exception:
exception.message = "The songbook file '{}' is not valid\n{}".format( exception.message = "The songbook file '{}' is not valid\n{}".format(
self.basename, exception.message) self.basename, exception.message)
raise exception raise exception
self._config['_compiled_authwords'] = authors.compile_authwords( tex_config['_compiled_authwords'] = authors.compile_authwords(
copy.deepcopy(self._config['authors']) copy.deepcopy(tex_config['authors'])
) )
# Configuration set # Configuration set
self._config['render'] = content.render tex_config['render'] = content.render
self._config['content'] = content.process_content( tex_config['_langs'], tex_config['content'] = self.get_content_items()
self._config.get('content', []), tex_config['filename'] = output.name[:-4]
self._config,
)
self._config['filename'] = output.name[:-4]
# Processing special options # Processing special options
self._config['_bookoptions'] = iter_bookoptions(self._config) tex_config['_bookoptions'] = iter_bookoptions(tex_config)
self._config['chords']['_notenames'] = self._get_chord_names( tex_config['chords']['_notenames'] = self._get_chord_names(
self._config['chords']['notation'] tex_config['chords']['notation']
) )
renderer.render_tex(output, self._config) renderer.render_tex(output, tex_config)
# Get all errors, and maybe exit program # Get all errors, and maybe exit program
self._errors.extend(renderer.errors) self._errors.extend(renderer.errors)
if self._config['_error'] == "failonbook": if tex_config['_error'] == "failonbook":
if self.has_errors(): if self.has_errors():
raise errors.SongbookError("Some songs contain errors. Stopping as requested.") raise errors.SongbookError("Some songs contain errors. Stopping as requested.")

4
patacrep/content/__init__.py

@ -116,6 +116,10 @@ class ContentItem:
"""Return the string to end a block.""" """Return the string to end a block."""
return "" return ""
def to_dict(self):
"""Return the dict representation (as in the yaml file)."""
raise NotImplementedError()
class ContentError(SharedError): class ContentError(SharedError):
"""Error in a content plugin.""" """Error in a content plugin."""
def __init__(self, keyword=None, message=None): def __init__(self, keyword=None, message=None):

6
patacrep/content/section.py

@ -28,6 +28,12 @@ class Section(ContentItem):
else: else:
return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name)
def to_dict(self):
if self.short is None or self.keyword not in KEYWORDS:
return {self.keyword: self.name}
else:
return {self.keyword: {'name': self.name, 'short': self.short}}
#pylint: disable=unused-argument #pylint: disable=unused-argument
@validate_parser_argument(""" @validate_parser_argument("""
type: //any type: //any

3
patacrep/content/setcounter.py

@ -14,6 +14,9 @@ class CounterSetter(ContentItem):
"""Set the value of the counter.""" """Set the value of the counter."""
return r'\setcounter{{{}}}{{{}}}'.format(self.name, self.value) return r'\setcounter{{{}}}{{{}}}'.format(self.name, self.value)
def to_dict(self):
return {'setcounter': {'name': self.name, 'value': self.value}}
#pylint: disable=unused-argument #pylint: disable=unused-argument
@validate_parser_argument(""" @validate_parser_argument("""
type: //any type: //any

3
patacrep/content/song.py

@ -63,6 +63,9 @@ class SongRenderer(ContentItem):
"""Order by song path""" """Order by song path"""
return self.song.fullpath < other.song.fullpath return self.song.fullpath < other.song.fullpath
def to_dict(self):
return {'song': self.song.fullpath}
#pylint: disable=unused-argument #pylint: disable=unused-argument
#pylint: disable=too-many-branches #pylint: disable=too-many-branches
@validate_parser_argument(""" @validate_parser_argument("""

3
patacrep/content/songsection.py

@ -19,6 +19,9 @@ class SongSection(ContentItem):
"""Render this section or chapter.""" """Render this section or chapter."""
return r'\{}{{{}}}'.format(self.keyword, self.name) return r'\{}{{{}}}'.format(self.keyword, self.name)
def to_dict(self):
return {self.keyword: self.name}
#pylint: disable=unused-argument #pylint: disable=unused-argument
@validate_parser_argument(""" @validate_parser_argument("""
//str //str

3
patacrep/content/tex.py

@ -20,6 +20,9 @@ class LaTeX(ContentItem):
os.path.dirname(context['filename']), os.path.dirname(context['filename']),
))) )))
def to_dict(self):
return {'tex': self.filename}
#pylint: disable=unused-argument #pylint: disable=unused-argument
@validate_parser_argument(""" @validate_parser_argument("""
type: //any type: //any

2
patacrep/songs/__init__.py

@ -43,7 +43,7 @@ class DataSubpath:
self.subpath = subpath self.subpath = subpath
def __str__(self): def __str__(self):
return os.path.join(self.datadir, self.subpath) return self.fullpath
@property @property
def fullpath(self): def fullpath(self):

13
patacrep/tools/__init__.py

@ -0,0 +1,13 @@
"""Common functions for patatools"""
import argparse
import os
def existing_file(name):
"""Check that argument is an existing, readable file name.
Return the argument for convenience.
"""
if os.path.isfile(name) and os.access(name, os.R_OK):
return name
raise argparse.ArgumentTypeError("Cannot read file '{}'.".format(name))

17
patacrep/tools/cache/__main__.py

@ -9,18 +9,10 @@ import textwrap
from patacrep import errors from patacrep import errors
from patacrep.songbook import open_songbook from patacrep.songbook import open_songbook
from .. import existing_file
LOGGER = logging.getLogger("patatools.cache") LOGGER = logging.getLogger("patatools.cache")
def filename(name):
"""Check that argument is an existing, readable file name.
Return the argument for convenience.
"""
if os.path.isfile(name) and os.access(name, os.R_OK):
return name
raise argparse.ArgumentTypeError("Cannot read file '{}'.".format(name))
def commandline_parser(): def commandline_parser():
"""Return a command line parser.""" """Return a command line parser."""
@ -30,10 +22,7 @@ def commandline_parser():
formatter_class=argparse.RawTextHelpFormatter, formatter_class=argparse.RawTextHelpFormatter,
) )
subparsers = parser.add_subparsers( subparsers = parser.add_subparsers()
description="",
dest="command",
)
subparsers.required = True subparsers.required = True
clean = subparsers.add_parser( clean = subparsers.add_parser(
@ -45,7 +34,7 @@ def commandline_parser():
'songbook', 'songbook',
metavar="SONGBOOK", metavar="SONGBOOK",
help=textwrap.dedent("""Songbook file to be used to look for cache path."""), help=textwrap.dedent("""Songbook file to be used to look for cache path."""),
type=filename, type=existing_file,
) )
clean.set_defaults(command=do_clean) clean.set_defaults(command=do_clean)

0
patacrep/tools/content/__init__.py

70
patacrep/tools/content/__main__.py

@ -0,0 +1,70 @@
"""Perform operations on songbook content."""
import argparse
import logging
import os
import sys
import textwrap
import yaml
from patacrep.songbook import open_songbook
from patacrep.build import Songbook
from .. import existing_file
LOGGER = logging.getLogger("patatools.content")
def commandline_parser():
"""Return a command line parser."""
parser = argparse.ArgumentParser(
prog="patatools content",
description="Operations related to the content of a songbook.",
formatter_class=argparse.RawTextHelpFormatter,
)
subparsers = parser.add_subparsers()
subparsers.required = True
content_items = subparsers.add_parser(
"items",
description="Display the content items of a songbook.",
help="Return the content items.",
)
content_items.add_argument(
'songbook',
metavar="SONGBOOK",
help=textwrap.dedent("""Songbook file to be used to look for content items."""),
type=existing_file,
)
content_items.set_defaults(command=do_content_items)
return parser
def do_content_items(namespace):
"""Execute the `patatools content items` command."""
config = open_songbook(namespace.songbook)
config['_cache'] = True
config['_error'] = "fix"
songbook = Songbook(config, config['_outputname'])
_, content_items = songbook.get_content_items()
yaml_dir = os.path.dirname(os.path.abspath(namespace.songbook))
ref_dir = os.path.join(yaml_dir, 'songs')
content_items = [
normalize_song_path(item.to_dict(), ref_dir)
for item in content_items
]
sys.stdout.write(yaml.safe_dump(content_items, allow_unicode=True, default_flow_style=False))
def normalize_song_path(file_entry, ref_dir):
"""Normalize the 'song' value, relative to ref_dir"""
if 'song' in file_entry:
file_entry['song'] = os.path.relpath(file_entry['song'], ref_dir)
return file_entry
def main(args):
"""Main function: run from command line."""
options = commandline_parser().parse_args(args[1:])
options.command(options)
if __name__ == "__main__":
main(sys.argv)

2
test/test_content/custom.control

@ -1 +1 @@
- "{'customname:': ''}" - customname: ''

2
test/test_content/custom.source

@ -1 +1 @@
- customname: - customname: ''

2
test/test_content/customzipped.control

@ -1 +1 @@
- "{'customzippedname:': ''}" - customzippedname: ''

2
test/test_content/customzipped.source

@ -1 +1 @@
- customzippedname: - customzippedname: ''

2
test/test_content/cwd.control

@ -1 +1 @@
- subdir/chordpro.csg - song: datadir/songs/subdir/chordpro.csg

4
test/test_content/cwd_list.control

@ -1,2 +1,2 @@
- subdir/chordpro.csg - song: datadir/songs/subdir/chordpro.csg
- exsong.sg - song: datadir/songs/exsong.sg

4
test/test_content/datadir/python/content/customplugin.py

@ -5,8 +5,8 @@ from patacrep.content import ContentItem, ContentList, validate_parser_argument
class FakeContent(ContentItem): class FakeContent(ContentItem):
"""Fake content.""" """Fake content."""
def file_entry(self): def to_dict(self):
return {'customname:':''} return {'customname':''}
def parse(keyword, argument, config): def parse(keyword, argument, config):
return ContentList([FakeContent()]) return ContentList([FakeContent()])

BIN
test/test_content/datadir_zippedcontent/python/content

Binary file not shown.

2
test/test_content/glob.control

@ -1 +1 @@
- chordpro.csg - song: datadir/songs/chordpro.csg

12
test/test_content/include.control

@ -1,6 +1,6 @@
- exsong.sg - song: datadir/songs/exsong.sg
- chordpro.csg - song: datadir/songs/chordpro.csg
- subdir/chordpro.csg - song: datadir/songs/subdir/chordpro.csg
- chordpro.csg - song: datadir/songs/chordpro.csg
- subdir/chordpro.csg - song: datadir/songs/subdir/chordpro.csg
- exsong.sg - song: datadir/songs/exsong.sg

24
test/test_content/sections.control

@ -1,11 +1,13 @@
- section{First Section!} - section: First Section!
- section{Named section} - section: Named section
- section[section_short_name]{Section with short name} - section:
- section*{Section* with short name} name: Section with short name
- part{part section test} short: section_short_name
- chapter{chapter section test} - section*: Section* with short name
- section{section section test} - part: part section test
- subsection{subsection section test} - chapter: chapter section test
- subsubsection{subsubsection section test} - section: section section test
- paragraph{paragraph section test} - subsection: subsection section test
- subparagraph{subparagraph section test} - subsubsection: subsubsection section test
- paragraph: paragraph section test
- subparagraph: subparagraph section test

16
test/test_content/setcounter.control

@ -1,4 +1,12 @@
- setcounter{songnum}{101} - setcounter:
- setcounter{songnum}{1} name: songnum
- setcounter{songnum}{5} value: 101
- setcounter{counter_name}{-1} - setcounter:
name: songnum
value: 1
- setcounter:
name: songnum
value: 5
- setcounter:
name: counter_name
value: -1

8
test/test_content/songs.control

@ -1,4 +1,4 @@
- exsong.sg - song: datadir/songs/exsong.sg
- texsong.tsg - song: datadir/songs/texsong.tsg
- chordpro.csg - song: datadir/songs/chordpro.csg
- subdir/chordpro.csg - song: datadir/songs/subdir/chordpro.csg

12
test/test_content/songsection.control

@ -1,6 +1,6 @@
- songsection{Traditional} - songsection: Traditional
- exsong.sg - song: datadir/songs/exsong.sg
- songchapter{English} - songchapter: English
- texsong.tsg - song: datadir/songs/texsong.tsg
- chordpro.csg - song: datadir/songs/chordpro.csg
- exsong.sg - song: datadir/songs/exsong.sg

54
test/test_content/sort.control

@ -1,27 +1,27 @@
- section{Title} - section: Title
- "@TEST_FOLDER@/datadir_sort/path1_title1_author1.csg" - song: datadir_sort/path1_title1_author1.csg
- "@TEST_FOLDER@/datadir_sort/path1_title1_author2.csg" - song: datadir_sort/path1_title1_author2.csg
- "@TEST_FOLDER@/datadir_sort/path2_title1_author1.csg" - song: datadir_sort/path2_title1_author1.csg
- "@TEST_FOLDER@/datadir_sort/path2_title1_author2.csg" - song: datadir_sort/path2_title1_author2.csg
- "@TEST_FOLDER@/datadir_sort/path1_title2_author1.csg" - song: datadir_sort/path1_title2_author1.csg
- "@TEST_FOLDER@/datadir_sort/path1_title2_author2.csg" - song: datadir_sort/path1_title2_author2.csg
- "@TEST_FOLDER@/datadir_sort/path2_title2_author1.csg" - song: datadir_sort/path2_title2_author1.csg
- "@TEST_FOLDER@/datadir_sort/path2_title2_author2.csg" - song: datadir_sort/path2_title2_author2.csg
- section{Author, Title} - section: Author, Title
- "@TEST_FOLDER@/datadir_sort/path1_title1_author1.csg" - song: datadir_sort/path1_title1_author1.csg
- "@TEST_FOLDER@/datadir_sort/path2_title1_author1.csg" - song: datadir_sort/path2_title1_author1.csg
- "@TEST_FOLDER@/datadir_sort/path1_title2_author1.csg" - song: datadir_sort/path1_title2_author1.csg
- "@TEST_FOLDER@/datadir_sort/path2_title2_author1.csg" - song: datadir_sort/path2_title2_author1.csg
- "@TEST_FOLDER@/datadir_sort/path1_title1_author2.csg" - song: datadir_sort/path1_title1_author2.csg
- "@TEST_FOLDER@/datadir_sort/path2_title1_author2.csg" - song: datadir_sort/path2_title1_author2.csg
- "@TEST_FOLDER@/datadir_sort/path1_title2_author2.csg" - song: datadir_sort/path1_title2_author2.csg
- "@TEST_FOLDER@/datadir_sort/path2_title2_author2.csg" - song: datadir_sort/path2_title2_author2.csg
- section{Path, Title} - section: Path, Title
- "@TEST_FOLDER@/datadir_sort/path1_title1_author1.csg" - song: datadir_sort/path1_title1_author1.csg
- "@TEST_FOLDER@/datadir_sort/path1_title1_author2.csg" - song: datadir_sort/path1_title1_author2.csg
- "@TEST_FOLDER@/datadir_sort/path1_title2_author1.csg" - song: datadir_sort/path1_title2_author1.csg
- "@TEST_FOLDER@/datadir_sort/path1_title2_author2.csg" - song: datadir_sort/path1_title2_author2.csg
- "@TEST_FOLDER@/datadir_sort/path2_title1_author1.csg" - song: datadir_sort/path2_title1_author1.csg
- "@TEST_FOLDER@/datadir_sort/path2_title1_author2.csg" - song: datadir_sort/path2_title1_author2.csg
- "@TEST_FOLDER@/datadir_sort/path2_title2_author1.csg" - song: datadir_sort/path2_title2_author1.csg
- "@TEST_FOLDER@/datadir_sort/path2_title2_author2.csg" - song: datadir_sort/path2_title2_author2.csg

40
test/test_content/test_content.py

@ -10,7 +10,6 @@ import yaml
from pkg_resources import resource_filename from pkg_resources import resource_filename
from patacrep import content, files from patacrep import content, files
from patacrep.content import song, section, setcounter, songsection, tex
from patacrep.songbook import prepare_songbook from patacrep.songbook import prepare_songbook
from .. import logging_reduced from .. import logging_reduced
@ -55,16 +54,13 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
with logging_reduced('patacrep.content.song'): with logging_reduced('patacrep.content.song'):
expandedlist = content.process_content(sbcontent, config) expandedlist = content.process_content(sbcontent, config)
sourcelist = [cls._clean_path(elem) for elem in expandedlist] sourcelist = [cls._clean_path(elem.to_dict()) for elem in expandedlist]
controlname = "{}.control".format(base) controlname = "{}.control".format(base)
if not os.path.exists(controlname): if not os.path.exists(controlname):
raise Exception("Missing control:" + str(sourcelist).replace("'", '"')) raise Exception("Missing control:" + str(controlname).replace("'", '"'))
with open(controlname, mode="r", encoding="utf8") as controlfile: with open(controlname, mode="r", encoding="utf8") as controlfile:
controllist = [ controllist = yaml.load(controlfile)
elem.replace("@TEST_FOLDER@", files.path2posix(resource_filename(__name__, "")))
for elem in yaml.load(controlfile)
]
self.assertEqual(controllist, sourcelist) self.assertEqual(controllist, sourcelist)
@ -75,25 +71,17 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
@classmethod @classmethod
def _clean_path(cls, elem): def _clean_path(cls, elem):
"""Shorten the path relative to the `songs` directory""" """Shorten the path relative to the test directory"""
if not isinstance(elem, dict):
latex_command_classes = ( return elem
section.Section,
songsection.SongSection, test_path = files.path2posix(resource_filename(__name__, ""))+"/"
setcounter.CounterSetter, for key in ['song', 'tex']:
) if key in elem:
if isinstance(elem, latex_command_classes): elem[key] = files.path2posix(
return elem.render(None)[1:] os.path.normpath(elem[key])
).replace(test_path, "")
elif isinstance(elem, song.SongRenderer): return elem
songpath = os.path.join(os.path.dirname(__file__), 'datadir', 'songs')
return files.path2posix(files.relpath(elem.song.fullpath, songpath))
elif isinstance(elem, tex.LaTeX):
return files.path2posix(elem.filename)
else:
return str(elem.file_entry())
@classmethod @classmethod
def _generate_config(cls, sbcontent, outputdir, base): def _generate_config(cls, sbcontent, outputdir, base):

6
test/test_content/tex.control

@ -1,3 +1,3 @@
- test/test_content/datadir/songs/texfile.tex - tex: test/test_content/datadir/songs/texfile.tex
- test/test_content/datadir/songs/texfile.tex - tex: test/test_content/datadir/songs/texfile.tex
- test/test_content/datadir/songs/texfile.tex - tex: test/test_content/datadir/songs/texfile.tex
Loading…
Cancel
Save