diff --git a/NEWS.md b/NEWS.md index 1bb8b80a..1966a401 100644 --- a/NEWS.md +++ b/NEWS.md @@ -19,6 +19,9 @@ * LaTeX songs * 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) + * Patatools + * New command to generate the list of the content items (songs, sections...): `patatools content items ` [#232](https://github.com/patacrep/patacrep/pull/232) + # patacrep 5.0.0 diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 5acc2aae..3edc0cf3 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -116,6 +116,10 @@ class ContentItem: """Return the string to end a block.""" return "" + def file_entry(self): + """Return the dict representation (as in the yaml file).""" + raise NotImplementedError() + class ContentError(SharedError): """Error in a content plugin.""" def __init__(self, keyword=None, message=None): diff --git a/patacrep/content/section.py b/patacrep/content/section.py index 316b0fa3..403e2072 100755 --- a/patacrep/content/section.py +++ b/patacrep/content/section.py @@ -28,6 +28,12 @@ class Section(ContentItem): else: return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) + def file_entry(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 @validate_parser_argument(""" type: //any diff --git a/patacrep/content/setcounter.py b/patacrep/content/setcounter.py index 41729b52..0b20b223 100755 --- a/patacrep/content/setcounter.py +++ b/patacrep/content/setcounter.py @@ -14,6 +14,9 @@ class CounterSetter(ContentItem): """Set the value of the counter.""" return r'\setcounter{{{}}}{{{}}}'.format(self.name, self.value) + def file_entry(self): + return {'setcounter': {'name': self.name, 'value': self.value}} + #pylint: disable=unused-argument @validate_parser_argument(""" type: //any diff --git a/patacrep/content/song.py b/patacrep/content/song.py index 3063a700..73ace8cf 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -63,6 +63,9 @@ class SongRenderer(ContentItem): """Order by song path""" return self.song.fullpath < other.song.fullpath + def file_entry(self): + return {'song': self.song.fullpath} + #pylint: disable=unused-argument #pylint: disable=too-many-branches @validate_parser_argument(""" diff --git a/patacrep/content/songsection.py b/patacrep/content/songsection.py index abc9d609..57d4e5e7 100755 --- a/patacrep/content/songsection.py +++ b/patacrep/content/songsection.py @@ -19,6 +19,9 @@ class SongSection(ContentItem): """Render this section or chapter.""" return r'\{}{{{}}}'.format(self.keyword, self.name) + def file_entry(self): + return {self.keyword: self.name} + #pylint: disable=unused-argument @validate_parser_argument(""" //str diff --git a/patacrep/content/tex.py b/patacrep/content/tex.py index b00f3539..9346e725 100755 --- a/patacrep/content/tex.py +++ b/patacrep/content/tex.py @@ -20,6 +20,9 @@ class LaTeX(ContentItem): os.path.dirname(context['filename']), ))) + def file_entry(self): + return {'tex': self.filename} + #pylint: disable=unused-argument @validate_parser_argument(""" type: //any diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index c58aaae9..0afcf9cf 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -43,7 +43,7 @@ class DataSubpath: self.subpath = subpath def __str__(self): - return os.path.join(self.datadir, self.subpath) + return self.fullpath @property def fullpath(self): diff --git a/patacrep/tools/content/__init__.py b/patacrep/tools/content/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/patacrep/tools/content/__main__.py b/patacrep/tools/content/__main__.py new file mode 100644 index 00000000..72cefc4e --- /dev/null +++ b/patacrep/tools/content/__main__.py @@ -0,0 +1,76 @@ +"""Perform operations on songbook content.""" + +import argparse +import logging +import os +import shutil +import sys +import textwrap +import yaml + +from patacrep import errors +from patacrep.songbook import open_songbook +from patacrep.build import Songbook + +LOGGER = logging.getLogger("patatools.content") + +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(): + """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( + description="", + dest="command", + ) + 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=filename, + ) + 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() + content_items = [item.file_entry() for item in content_items] + print(yaml.safe_dump(content_items, allow_unicode=True, default_flow_style=False)) + +def main(args): + """Main function: run from command line.""" + options = commandline_parser().parse_args(args[1:]) + try: + options.command(options) + except errors.SongbookError as error: + LOGGER.error(str(error)) + sys.exit(1) + +if __name__ == "__main__": + main(sys.argv)