diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c35513e7..2ba56bfa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.64 +current_version = 0.3.65 [bumpversion:file:mdpo/__init__.py] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ae33b56..37445131 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,12 +9,16 @@ repos: rev: v4.0.1 hooks: - id: trailing-whitespace + name: trailing-whitespace - id: end-of-file-fixer + name: end-of-file-fixer - id: double-quote-string-fixer + name: double-quote-string-fixer - repo: https://github.com/asottile/add-trailing-comma rev: v2.1.0 hooks: - id: add-trailing-comma + name: add-trailing-comma args: - --py36-plus - repo: https://github.com/asottile/setup-cfg-fmt diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 00000000..4cf06a66 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,5 @@ +- id: md2po2md + name: md2po2md + entry: md2po2md + description: Translates Markdown files using PO files for a set of predefined language codes creating multiple directories, one for each language + language: python diff --git a/.yamllint b/.yamllint index d7d828a4..63b008ad 100644 --- a/.yamllint +++ b/.yamllint @@ -29,13 +29,15 @@ rules: key-ordering: ignore: | .pre-commit-config.yaml + .pre-commit-hooks.yaml .github .readthedocs.yml test/test_po2md/.markdownlint-cli2.yaml line-length: allow-non-breakable-words: true - ignore: + ignore: | .pre-commit-config.yaml + .pre-commit-hooks.yaml max: 96 new-lines: type: unix diff --git a/docs/api.rst b/docs/api.rst index 48526468..9af04f84 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -16,6 +16,12 @@ po2md .. automodule:: mdpo.po2md :members: pofile_to_markdown +md2po2md +======== + +.. automodule:: mdpo.md2po2md + :members: markdown_to_pofile_to_markdown + mdpo2html ========= diff --git a/docs/cli.rst b/docs/cli.rst index 022b174f..3c9428ea 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -60,6 +60,21 @@ The output produced by :ref:`po2md-cli` is compatible with the following
+.. _md2po2md-cli: + +md2po2md +======== + +.. sphinx_argparse_cli:: + :module: mdpo.md2po2md.__main__ + :func: build_parser + :prog: md2po2md + :title: + +.. raw:: html + +
+ .. _mdpo2html-cli: mdpo2html diff --git a/docs/devref/index.rst b/docs/devref/index.rst index 76ff6086..1c219cce 100644 --- a/docs/devref/index.rst +++ b/docs/devref/index.rst @@ -17,3 +17,4 @@ mdpo/ mdpo/md2po/mdpo.md2po.index mdpo/mdpo2html/mdpo.mdpo2html.index mdpo/po2md/mdpo.po2md.index + mdpo/md2po2md/mdpo.md2po2md.index diff --git a/docs/devref/mdpo/md2po2md/mdpo.md2po2md.__init__.rst b/docs/devref/mdpo/md2po2md/mdpo.md2po2md.__init__.rst new file mode 100644 index 00000000..7ebb5a4f --- /dev/null +++ b/docs/devref/mdpo/md2po2md/mdpo.md2po2md.__init__.rst @@ -0,0 +1,6 @@ +*********** +__init__.py +*********** + +.. autofunction:: mdpo.md2po2md.markdown_to_pofile_to_markdown + :noindex: diff --git a/docs/devref/mdpo/md2po2md/mdpo.md2po2md.__main__.rst b/docs/devref/mdpo/md2po2md/mdpo.md2po2md.__main__.rst new file mode 100644 index 00000000..550dd319 --- /dev/null +++ b/docs/devref/mdpo/md2po2md/mdpo.md2po2md.__main__.rst @@ -0,0 +1,6 @@ +*********** +__main__.py +*********** + +.. automodule:: mdpo.md2po2md.__main__ + :members: diff --git a/docs/devref/mdpo/md2po2md/mdpo.md2po2md.index.rst b/docs/devref/mdpo/md2po2md/mdpo.md2po2md.index.rst new file mode 100644 index 00000000..a891f75d --- /dev/null +++ b/docs/devref/mdpo/md2po2md/mdpo.md2po2md.index.rst @@ -0,0 +1,9 @@ +********* +md2po2md/ +********* + +.. toctree:: + :maxdepth: 2 + + mdpo.md2po2md.__init__ + mdpo.md2po2md.__main__ diff --git a/docs/index.rst b/docs/index.rst index 3c98b015..602f487d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,7 +14,6 @@ Markdown files translation using pofiles. Fully complies with tutorial commands related-utilities - useful-recipes .. toctree:: :maxdepth: 2 @@ -22,6 +21,7 @@ Markdown files translation using pofiles. Fully complies with cli api + pc-hooks changelog .. toctree:: diff --git a/docs/pc-hooks.rst b/docs/pc-hooks.rst new file mode 100644 index 00000000..8981a1be --- /dev/null +++ b/docs/pc-hooks.rst @@ -0,0 +1,22 @@ +**************** +pre-commit hooks +**************** + +md2po2md +======== + +.. code-block:: yaml + + - repo: https://github.com/mondeja/mdpo + rev: v0.3.65 + hooks: + - id: md2po2md + args: + - prueba.md + - -l + - es + - -o + - locale/{lang} + +.. seealso:: + * :ref:`md2po2md CLI` diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e02fca9f..8910e87d 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -61,6 +61,39 @@ This will be the output after previous two commands:
+Simple README file translation +============================== + +Just use :ref:`md2po2md CLI`: + +.. code-block:: bash + + md2po2md README.md -l es -l fr -o "locale/{lang}" + +Define the languages to translate into using the ``-l`` option. + +You also can use the next snippet to include links for the translations: + +.. code-block:: html + + + + > Read this document in other languages: + > + > - [Español][readme-es] + > - [Français][readme-fr] + + + [readme-es]: https://github.com/user/repo/blob/master/locale/es/README.md + [readme-fr]: https://github.com/user/repo/blob/master/locale/fr/README.md + +.. seealso:: + * :ref:`md2po2md CLI` + +.. raw:: html + +
+ HTML-from-Markdown to HTML ========================== diff --git a/docs/useful-recipes.rst b/docs/useful-recipes.rst deleted file mode 100644 index 79666054..00000000 --- a/docs/useful-recipes.rst +++ /dev/null @@ -1,30 +0,0 @@ -************** -Useful recipes -************** - -Simple README file translation -============================== - -I think that this is the simplest way that I've found to translate the READMEs -of my projects. Just create a folder ``locale/`` with language folders inside -and execute the next line (posix shell): - -.. code-block:: bash - - langs=( es ); for l in "${langs[@]}"; do md2po -qs README.md -po locale/$l/README.po && po2md -q README.md -s locale/$l/README.md -p locale/$l/README.po; done - -Define the languages to translate into using the ``langs`` variable separating -them by spaces. - -You also can use the next snippet to include links for the translations: - -.. code-block:: html - - - - > Read this document in other languages: - > - > - [Español][readme-es] - - - [readme-es]: https://github.com/user/repo/blob/master/locale/es/README.md diff --git a/mdpo/__init__.py b/mdpo/__init__.py index 61a9f5ba..d5243071 100644 --- a/mdpo/__init__.py +++ b/mdpo/__init__.py @@ -5,7 +5,7 @@ from mdpo.po2md import pofile_to_markdown -__version__ = '0.3.64' +__version__ = '0.3.65' __title__ = 'mdpo' __description__ = ('Markdown file translation utilities using PO files') __all__ = ( diff --git a/mdpo/cli.py b/mdpo/cli.py index cb4c8f0c..6b7a7bfe 100644 --- a/mdpo/cli.py +++ b/mdpo/cli.py @@ -4,6 +4,7 @@ import sys from mdpo import __version__ +from mdpo.md4c import DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS from mdpo.text import parse_escaped_pairs @@ -87,12 +88,13 @@ def parse_metadata_cli_arguments(metadata): ) -def add_common_cli_first_arguments(parser): - """Adds common mdpo arguments to an argument parser at the beginning. +def add_common_cli_first_arguments(parser, quiet=True): + """Add common mdpo arguments to an argument parser at the beginning. Args: parser (:py:class:`argparse.ArgumentParser`): Arguments parser to extend. + quiet (bool): Include the argument ``-q/--quiet``. """ parser.add_argument( '-h', '--help', action='help', @@ -104,14 +106,15 @@ def add_common_cli_first_arguments(parser): version=f'%(prog)s {__version__}', help='Show program version number and exit.', ) - parser.add_argument( - '-q', '--quiet', action='store_true', - help='Do not print output to STDOUT.', - ) + if quiet: + parser.add_argument( + '-q', '--quiet', action='store_true', + help='Do not print output to STDOUT.', + ) def add_common_cli_latest_arguments(parser): - """Adds common mdpo arguments to an argument parser at the end. + """Add common mdpo arguments to an argument parser at the end. Args: parser (:py:class:`argparse.ArgumentParser`): Arguments parser to @@ -128,3 +131,53 @@ def add_common_cli_latest_arguments(parser): ' you can pass either \'--command-alias "mdpo-on:mdpo-enable"\'' ' or \'--command-alias "mdpo-on:enable"\' arguments.', ) + + +def add_extensions_argument(parser): + """Add the ``-x/--extension`` argument to an argument parser. + + Args: + parser (:py:class:`argparse.ArgumentParser`): Arguments parser to + extend. + """ + parser.add_argument( + '-x', '--extension', '--ext', dest='extensions', action='append', + default=None, + help='md4c extension used to parse markdown content formatted as' + ' pymd4c extension keyword arguments. This argument can be passed' + ' multiple times. If is not passed, next extensions are used:' + f' {", ".join(DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS)}.' + ' You can see all available at' + ' https://github.com/dominickpastore/pymd4c#parser-option-flags', + metavar='EXTENSION', + ) + + +def add_debug_option(parser): + """Add the ``-D/--debug`` option to an argument parser. + + Args: + parser (:py:class:`argparse.ArgumentParser`): Arguments parser to + extend. + """ + parser.add_argument( + '-D', '--debug', dest='debug', action='store_true', + help='Print useful messages in the parsing process showing the' + ' contents of all Markdown elements.', + ) + + +def add_nolocation_option(parser): + """Add the ``--no-location/--nolocation`` option to an argument parser. + + Args: + parser (:py:class:`argparse.ArgumentParser`): Arguments parser to + extend. + """ + parser.add_argument( + '--no-location', '--nolocation', dest='location', action='store_false', + help="Do not write '#: filename:line' lines. Note that using this" + ' option makes it harder for technically skilled translators to' + " understand each message's context. Same as 'xgettext " + "--no-location'.", + ) diff --git a/mdpo/md2po/__main__.py b/mdpo/md2po/__main__.py index 3015dcc3..55d9d3e0 100755 --- a/mdpo/md2po/__main__.py +++ b/mdpo/md2po/__main__.py @@ -9,6 +9,9 @@ from mdpo.cli import ( add_common_cli_first_arguments, add_common_cli_latest_arguments, + add_debug_option, + add_extensions_argument, + add_nolocation_option, parse_command_aliases_cli_arguments, parse_metadata_cli_arguments, ) @@ -91,24 +94,8 @@ def build_parser(): ' passed as \'-po/--po-filepath\' parameter will be removed.' ' Only has effect used in combination with \'--merge-pofiles\'.', ) - parser.add_argument( - '--no-location', '--nolocation', dest='location', action='store_false', - help="Do not write '#: filename:line' lines. Note that using this" - ' option makes it harder for technically skilled translators to' - " understand each message's context. Same as 'xgettext " - "--no-location'.", - ) - parser.add_argument( - '-x', '--extension', '--ext', dest='extensions', action='append', - default=None, - help='md4c extension used to parse markdown content formatted as' - ' pymd4c extension keyword arguments. This argument can be passed' - ' multiple times. If is not passed, next extensions are used:' - f' {", ".join(DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS)}.' - ' You can see all available at' - ' https://github.com/dominickpastore/pymd4c#parser-option-flags', - metavar='', - ) + add_nolocation_option(parser) + add_extensions_argument(parser) parser.add_argument( '--po-encoding', dest='po_encoding', default=None, help='Resulting PO file encoding.', metavar='', @@ -147,12 +134,8 @@ def build_parser(): ' \'-d "Content-Type: text/plain; charset=utf-8"' ' -d "Language: es"\'.', ) - parser.add_argument( - '-D', '--debug', dest='debug', action='store_true', - help='Print useful messages in the parsing process showing the' - ' contents of all Markdown elements.', - ) add_common_cli_latest_arguments(parser) + add_debug_option(parser) return parser @@ -213,10 +196,9 @@ def run(args=[]): ignore_msgids=opts.ignore_msgids, command_aliases=opts.command_aliases, metadata=opts.metadata, + wrapwidth=opts.wrapwidth, debug=opts.debug, ) - if isinstance(opts.wrapwidth, int): - kwargs['wrapwidth'] = opts.wrapwidth pofile = markdown_to_pofile(opts.glob_or_content, **kwargs) diff --git a/mdpo/md2po2md/__init__.py b/mdpo/md2po2md/__init__.py new file mode 100644 index 00000000..97ac14d4 --- /dev/null +++ b/mdpo/md2po2md/__init__.py @@ -0,0 +1,137 @@ +"""Markdown to PO file to Markdown translator.""" + +import glob +import os +import re + +from mdpo.md2po import markdown_to_pofile +from mdpo.md4c import DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS +from mdpo.po2md import pofile_to_markdown + + +def markdown_to_pofile_to_markdown( + langs, + input_paths_glob, + output_paths_schema, + extensions=DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS, + command_aliases={}, + location=True, + debug=False, + md2po_kwargs={}, + po2md_kwargs={}, +): + """Translate a set of Markdown files using PO files. + + Args: + langs (list): List of languages used to build the output directories. + input_paths_glob (str): Glob covering Markdown files to translate. + output_paths_schema (str): Path schema for outputs, built using + placeholders. There is a mandatory placeholder for languages: + ``{lang}``; and one optional for output basename: ``{basename}``. + For example, for the schema ``locale/{lang}``, the languages + ``['es', 'fr']`` and a ``README.md`` as input, the next files will + be written: + + * ``locale/es/README.po`` + * ``locale/es/README.md`` + * ``locale/fr/README.po`` + * ``locale/fr/README.md`` + + Note that you can omit ``{basename}``, specifying a directory for + each language with ``locale/{lang}`` for this example. + Unexistent directories and files will be created, so you don't + have to prepare the output directories before the execution. + extensions (list): md4c extensions used to parse markdown content, + formatted as a list of 'pymd4c' keyword arguments. You can see all + available at `pymd4c repository `_. + command_aliases (dict): Mapping of aliases to use custom mdpo command + names in comments. The ``mdpo-`` prefix in command names resolution + is optional. For example, if you want to use ```` + instead of ````, you can pass the dictionaries + ``{"mdpo-on": "mdpo-enable"}`` or ``{"mdpo-on": "enable"}`` to this + parameter. + location (bool): Store references of top-level blocks in which are + found the messages in PO file `#: reference` comments. + debug (bool): Add events displaying all parsed elements in the + extraction process. + md2po_kwargs (dict): Additional optional arguments passed to + ``markdown_to_pofile`` function. + po2md_kwargs (dict): Additional optional arguments passed to + ``pofile_to_markdown`` function. + """ + if '{lang}' not in output_paths_schema: + raise ValueError( + "You must pass the replacer '{lang}' inside the argument" + " 'output_paths_schema'.", + ) + + try: + input_paths_glob_ = glob.glob(input_paths_glob) + except re.error: + # some strings like '[s-m]' will produce + # 're.error: bad character range ... at position' + raise ValueError( + "The argument 'input_paths_glob' must be a valid glob or file" + ' path.', + ) + else: + if not input_paths_glob_: + raise FileNotFoundError( + f'The glob \'{input_paths_glob}\' does not match any file.', + ) + + for filepath in input_paths_glob_: + for lang in langs: + md_ext = os.path.splitext(filepath)[-1] + + file_basename = os.path.splitext(os.path.basename(filepath))[0] + + format_kwargs = {'lang': lang} + if '{basename}' in output_paths_schema: + format_kwargs['basename'] = file_basename + po_filepath = output_paths_schema.format(**format_kwargs) + + po_basename = os.path.basename(po_filepath) + po_dirpath = ( + os.path.dirname(po_filepath) + if (po_basename.count('.') or file_basename == po_basename) + else po_filepath + ) + + os.makedirs(os.path.abspath(po_dirpath), exist_ok=True) + if os.path.isdir(po_filepath): + po_filepath = ( + po_filepath.rstrip(os.sep) + os.sep + + os.path.basename(filepath) + '.po' + ) + if not po_filepath.endswith('.po'): + po_filepath += '.po' + + format_kwargs['ext'] = md_ext.lstrip('.') + md_filepath = output_paths_schema.format(**format_kwargs) + if os.path.isdir(md_filepath): + md_filepath = ( + md_filepath.rstrip(os.sep) + os.sep + + os.path.basename(filepath) + ) + + markdown_to_pofile( + filepath, + save=True, + po_filepath=po_filepath, + extensions=extensions, + command_aliases=command_aliases, + debug=debug, + location=location, + **md2po_kwargs, + ) + + pofile_to_markdown( + filepath, + [po_filepath], + save=md_filepath, + command_aliases=command_aliases, + debug=debug, + **po2md_kwargs, + ) diff --git a/mdpo/md2po2md/__main__.py b/mdpo/md2po2md/__main__.py new file mode 100644 index 00000000..8424be06 --- /dev/null +++ b/mdpo/md2po2md/__main__.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +"""md2po2md command line interface.""" + +import argparse +import os +import sys + +from mdpo.cli import ( + add_common_cli_first_arguments, + add_common_cli_latest_arguments, + add_debug_option, + add_extensions_argument, + add_nolocation_option, + parse_command_aliases_cli_arguments, +) +from mdpo.md2po2md import markdown_to_pofile_to_markdown +from mdpo.md4c import DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS + + +DESCRIPTION = ( + 'Translates Markdown files using PO files for a set of predefined language' + ' codes creating multiple directories, one for each language.' +) + + +def build_parser(): + parser = argparse.ArgumentParser(description=DESCRIPTION, add_help=False) + add_common_cli_first_arguments(parser, quiet=False) + parser.add_argument( + 'input_paths_glob', metavar='GLOB', nargs='*', + help='Glob to markdown input files to translate.' + ' If not provided, will be read from STDIN.', + ) + parser.add_argument( + '-l', '--lang', dest='langs', default=[], action='append', + help='Language codes used to create the output directories.' + ' Can be passed multiple times.', + metavar='LANG', + ) + parser.add_argument( + '-o', '--output', dest='output_paths_schema', + required=True, type=str, + help='Path schema for outputs, built using placeholders. There is a' + ' mandatory placeholder for languages: \'{lang}\'; and one' + ' optional for output basename: \'{basename}\'. For example,' + ' for the schema \'locale/{lang}\', the languages \'es\' and' + ' \'fr\' and a \'README.md\' as input, the next files will be' + ' written: \'locale/es/README.po\', \'locale/es/README.md\',' + ' \'locale/fr/README.po\' and \'locale/fr/README.md\'.' + ' Note that you can omit \'{basename}\', specifying a' + ' directory for each language with \'locale/{lang}\' for this' + ' example. Unexistent directories and files will be created, ' + ' so you don\'t have to prepare the output directories before' + ' the execution.', + metavar='PATH_SCHEMA', + ) + add_nolocation_option(parser) + add_extensions_argument(parser) + add_common_cli_latest_arguments(parser) + add_debug_option(parser) + return parser + + +def parse_options(args=[]): + parser = build_parser() + if '-h' in args or '--help' in args: + parser.print_help() + sys.exit(1) + opts, unknown = parser.parse_known_args(args) + + input_paths_glob = '' + if not sys.stdin.isatty(): + input_paths_glob += sys.stdin.read().strip('\n') + if isinstance(opts.input_paths_glob, list) and opts.input_paths_glob: + input_paths_glob += opts.input_paths_glob[0] + opts.input_paths_glob = input_paths_glob + + if opts.extensions is None: + opts.extensions = DEFAULT_MD4C_GENERIC_PARSER_EXTENSIONS + + opts.command_aliases = parse_command_aliases_cli_arguments( + opts.command_aliases, + ) + + return opts + + +def run(args=[]): + prev_mdpo_running = os.environ.get('_MDPO_RUNNING') + os.environ['_MDPO_RUNNING'] = 'true' + + try: + opts = parse_options(args) + + kwargs = dict( + extensions=opts.extensions, + command_aliases=opts.command_aliases, + debug=opts.debug, + location=opts.location, + ) + + markdown_to_pofile_to_markdown( + opts.langs, + opts.input_paths_glob, + opts.output_paths_schema, + **kwargs, + ) + finally: + if prev_mdpo_running is None: + del os.environ['_MDPO_RUNNING'] + else: + os.environ['_MDPO_RUNNING'] = prev_mdpo_running + return 0 + + +def main(): + sys.exit(run(args=sys.argv[1:])) # pragma: no cover + + +if __name__ == '__main__': + main() diff --git a/mdpo/po2md/__main__.py b/mdpo/po2md/__main__.py index 9f79cb77..796a898b 100644 --- a/mdpo/po2md/__main__.py +++ b/mdpo/po2md/__main__.py @@ -10,6 +10,7 @@ from mdpo.cli import ( add_common_cli_first_arguments, add_common_cli_latest_arguments, + add_debug_option, parse_command_aliases_cli_arguments, ) from mdpo.po2md import pofile_to_markdown @@ -72,12 +73,8 @@ def build_parser(): ' charset=\\n\'.', metavar='', ) - parser.add_argument( - '-D', '--debug', dest='debug', action='store_true', - help='Print useful messages in the parsing process showing the' - ' contents of all Markdown elements.', - ) add_common_cli_latest_arguments(parser) + add_debug_option(parser) return parser diff --git a/setup.cfg b/setup.cfg index a982b37b..eaf9b840 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = mdpo -version = 0.3.64 +version = 0.3.65 description = Markdown files translation using PO files. long_description = file: README.md long_description_content_type = text/markdown @@ -34,6 +34,7 @@ project_urls = packages = mdpo mdpo.md2po + mdpo.md2po2md mdpo.mdpo2html mdpo.po2md install_requires = @@ -46,6 +47,7 @@ include_package_data = True console_scripts = md2po = mdpo.md2po.__main__:main po2md = mdpo.po2md.__main__:main + md2po2md = mdpo.md2po2md.__main__:main mdpo2html = mdpo.mdpo2html.__main__:main [options.extras_require] @@ -95,6 +97,7 @@ per-file-ignores = mdpo/md2po/__main__.py: D103 mdpo/po2md/__init__.py: D101, D102, D103, D107 mdpo/po2md/__main__.py: D103 + mdpo/md2po2md/__main__.py: D103 mdpo/mdpo2html/__init__.py: D101, D102, D103, D107, D205, D415 mdpo/mdpo2html/__main__.py: D103 docstring-convention = google diff --git a/test/test_md2po2md/test_md2po2md_cli.py b/test/test_md2po2md/test_md2po2md_cli.py new file mode 100644 index 00000000..e19f6eff --- /dev/null +++ b/test/test_md2po2md/test_md2po2md_cli.py @@ -0,0 +1,179 @@ +"""Tests for ``md2po2md`` CLI""" + +import os +import shutil +import tempfile +from uuid import uuid4 + +import pytest + +from mdpo.md2po2md.__main__ import run + + +@pytest.mark.parametrize('output_arg', ('-o', '--output')) +@pytest.mark.parametrize('langs_arg', ('-l', '--lang')) +@pytest.mark.parametrize( + ( + 'langs', + 'input_paths_glob', + 'output', + 'input_files_content', + 'expected_files_content', + ), + ( + pytest.param( + ['es'], + 'README.md', + 'locale/{lang}', + {'README.md': 'Foo\n\nBar\n'}, + { + 'locale/es/README.md.po': ( + '#\nmsgid ""\nmsgstr ""\n\nmsgid "Foo"\nmsgstr ""\n\n' + 'msgid "Bar"\nmsgstr ""\n' + ), + 'locale/es/README.md': 'Foo\n\nBar\n', + }, + id='es-locale/{lang}', + ), + pytest.param( + ['es', 'fr'], + 'README.md', + 'locale/{lang}', + {'README.md': 'Foo\n\nBar\n'}, + { + 'locale/es/README.md.po': ( + '#\nmsgid ""\nmsgstr ""\n\nmsgid "Foo"\nmsgstr ""\n\n' + 'msgid "Bar"\nmsgstr ""\n' + ), + 'locale/es/README.md': 'Foo\n\nBar\n', + 'locale/fr/README.md.po': ( + '#\nmsgid ""\nmsgstr ""\n\nmsgid "Foo"\nmsgstr ""\n\n' + 'msgid "Bar"\nmsgstr ""\n' + ), + 'locale/fr/README.md': 'Foo\n\nBar\n', + }, + id='README-es,fr-locale/{lang}', + ), + pytest.param( + ['es'], + 'README.md', + 'locale/', + {}, + ValueError, + id='es-locale/-ValueError', + ), + pytest.param( + ['es'], + 'README.md', + 'locale/{lang}/{basename}', + {'README.md': 'Foo\n\nBar\n'}, + { + 'locale/es/README.po': ( + '#\nmsgid ""\nmsgstr ""\n\nmsgid "Foo"\nmsgstr ""\n\n' + 'msgid "Bar"\nmsgstr ""\n' + ), + 'locale/es/README': 'Foo\n\nBar\n', + }, + id='es-locale/{lang}/{basename}', + ), + pytest.param( + ['es'], + 'README.md', + '{lang}/{basename}', + {'README.md': 'Foo\n\nBar\n'}, + { + 'es/README.po': ( + '#\nmsgid ""\nmsgstr ""\n\nmsgid "Foo"\nmsgstr ""\n\n' + 'msgid "Bar"\nmsgstr ""\n' + ), + 'es/README': 'Foo\n\nBar\n', + }, + id='es-{lang}/{basename}', + ), + pytest.param( + ['es'], + 'README.md', + '{lang}', + {'README.md': 'Foo\n\nBar\n'}, + { + 'es/README.md.po': ( + '#\nmsgid ""\nmsgstr ""\n\nmsgid "Foo"\nmsgstr ""\n\n' + 'msgid "Bar"\nmsgstr ""\n' + ), + 'es/README.md': 'Foo\n\nBar\n', + }, + id='es-{lang}', + ), + pytest.param( + ['es'], + '[s-m]', + '{lang}/{basename}', + {}, + ValueError, + id='invalid-glob-ValueError', + ), + pytest.param( + ['es'], + 'foobar*', + '{lang}/{basename}', + {}, + FileNotFoundError, + id='no-files-matching-input-glob', + ), + ), +) +def test_md2po2md_arguments( + output_arg, + langs_arg, + langs, + input_paths_glob, + output, + input_files_content, + expected_files_content, +): + # create base directory and files + basedir = os.path.join(tempfile.gettempdir(), uuid4().hex[:8]) + if os.path.isdir(basedir): + shutil.rmtree(basedir) + os.mkdir(basedir) + + for relpath, content in input_files_content.items(): + filepath = os.path.join(basedir, relpath) + with open(filepath, 'w') as f: + f.write(content) + + def cleanup(): + if os.path.isdir(basedir): + shutil.rmtree(basedir) + + # run md2po2md + cmd = [ + os.path.join(basedir, input_paths_glob), + output_arg, os.path.join(basedir, output), + ] + for lang in langs: + cmd.extend([langs_arg, lang]) + cmd.append('--no-location') + + if hasattr(expected_files_content, '__traceback__'): + with pytest.raises(expected_files_content): + run(cmd) + return cleanup() + run(cmd) + + # Check number of files + expected_number_of_files = ( + len(expected_files_content.keys()) + len(input_files_content.keys()) + ) + n_files = 0 + for root, dirs, files in os.walk(basedir, topdown=False): + n_files += len(files) + assert n_files == expected_number_of_files + + # Check expected content + for relpath, content in expected_files_content.items(): + filepath = os.path.join(basedir, relpath) + with open(filepath) as f: + assert f.read() == content + + cleanup()