Browse Source

Merge master

pull/122/head
Louis 9 years ago
parent
commit
1876b5b5b4
  1. 1
      examples/.gitignore
  2. 3
      examples/example-all.sb
  3. 2
      examples/songs/greensleeves.sgc
  4. 2
      examples/songs/tests/chords.sgc
  5. 2
      examples/songs/tests/errors.sgc
  6. 2
      patacrep/build.py
  7. 6
      patacrep/content/song.py
  8. 10
      patacrep/data/templates/songs.tex
  9. 23
      patacrep/latex/__init__.py
  10. 6
      patacrep/latex/ast.py
  11. 2
      patacrep/songbook.py
  12. 6
      patacrep/songs/__init__.py
  13. 6
      patacrep/songs/chordpro/__init__.py
  14. 4
      patacrep/songs/chordpro/ast.py
  15. 4
      patacrep/songs/chordpro/data/chordpro/song_header
  16. 4
      patacrep/songs/chordpro/data/html/song_header
  17. 4
      patacrep/songs/chordpro/data/latex/song
  18. 20
      patacrep/songs/latex/__init__.py
  19. 4
      patacrep/templates.py
  20. 2
      test/test_chordpro/00.sgc
  21. 2
      test/test_chordpro/01.sgc
  22. 2
      test/test_chordpro/02.sgc
  23. 2
      test/test_chordpro/03.sgc
  24. 2
      test/test_chordpro/04.sgc
  25. 2
      test/test_chordpro/05.sgc
  26. 2
      test/test_chordpro/06.sgc
  27. 2
      test/test_chordpro/07.sgc
  28. 2
      test/test_chordpro/08.sgc
  29. 2
      test/test_chordpro/09.sgc
  30. 2
      test/test_chordpro/10.sgc
  31. 2
      test/test_chordpro/11.sgc
  32. 2
      test/test_chordpro/12.sgc
  33. 2
      test/test_chordpro/13.sgc
  34. 2
      test/test_chordpro/21.sgc
  35. 2
      test/test_chordpro/22.sgc
  36. 2
      test/test_chordpro/23.sgc
  37. 2
      test/test_chordpro/24.sgc
  38. 2
      test/test_chordpro/25.sgc
  39. 2
      test/test_chordpro/26.sgc
  40. 2
      test/test_chordpro/27.sgc
  41. 2
      test/test_chordpro/28.sgc
  42. 2
      test/test_chordpro/29.sgc
  43. 2
      test/test_chordpro/author_names.sgc
  44. 2
      test/test_chordpro/chords.sgc
  45. 2
      test/test_chordpro/customchords.sgc
  46. 2
      test/test_chordpro/greensleeves.sgc
  47. 2
      test/test_chordpro/greensleeves.source
  48. 2
      test/test_chordpro/invalid_chord.sgc
  49. 2
      test/test_chordpro/invalid_customchord.sgc
  50. 1
      test/test_chordpro/lang.sgc
  51. 1
      test/test_chordpro/lang.source
  52. 2
      test/test_chordpro/metadata.sgc
  53. 4
      test/test_chordpro/metadata.source
  54. 2
      test/test_chordpro/newline.html
  55. 2
      test/test_chordpro/newline.sgc
  56. 2
      test/test_chordpro/nolyrics.sgc
  57. 2
      test/test_chordpro/ukulelechords.sgc

1
examples/.gitignore

@ -1 +1,2 @@
/.cache /.cache
/*tex

3
examples/example-all.sb

@ -6,8 +6,9 @@
"pictures" "pictures"
], ],
"booktype" : "chorded", "booktype" : "chorded",
"datadir": ["datadir2"],
"template" : "patacrep.tex", "template" : "patacrep.tex",
"lang" : "french", "lang" : "fr",
"encoding": "utf8", "encoding": "utf8",
"authwords" : { "authwords" : {
"sep" : ["and", "et"] "sep" : ["and", "et"]

2
examples/songs/greensleeves.sgc

@ -1,4 +1,4 @@
{language : english} {lang : en}
{columns : 2} {columns : 2}
{ title : Greensleeves} { title : Greensleeves}
{subtitle: Test of the chordpro format} {subtitle: Test of the chordpro format}

2
examples/songs/tests/chords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{columns: 1} {columns: 1}
{title: Chords testing} {title: Chords testing}
{subtitle: Test of the chords specification and LaTeX translation} {subtitle: Test of the chords specification and LaTeX translation}

2
examples/songs/tests/errors.sgc

@ -1,4 +1,4 @@
{language : english} {lang : en}
{columns : 2} {columns : 2}
{ title : Error} { title : Error}
{subtitle: A chordpro file with many errors} {subtitle: A chordpro file with many errors}

2
patacrep/build.py

@ -29,7 +29,7 @@ GENERATED_EXTENSIONS = [
] ]
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
'template': "default.tex", 'template': "default.tex",
'lang': 'english', 'lang': 'en',
'content': [], 'content': [],
'titleprefixwords': [], 'titleprefixwords': [],
'encoding': None, 'encoding': None,

6
patacrep/content/song.py

@ -60,8 +60,8 @@ def parse(keyword, argument, contentlist, config):
Return a list of Song() instances. Return a list of Song() instances.
""" """
plugins = config['_song_plugins'] plugins = config['_song_plugins']
if '_languages' not in config: if '_langs' not in config:
config['_languages'] = set() config['_langs'] = set()
songlist = [] songlist = []
for songdir in config['_songdir']: for songdir in config['_songdir']:
if contentlist: if contentlist:
@ -92,7 +92,7 @@ def parse(keyword, argument, contentlist, config):
datadir=songdir.datadir, datadir=songdir.datadir,
)) ))
songlist.append(renderer) songlist.append(renderer)
config["_languages"].update(renderer.song.languages) config["_langs"].add(renderer.song.lang)
if len(songlist) > before: if len(songlist) > before:
break break
if len(songlist) == before: if len(songlist) == before:

10
patacrep/data/templates/songs.tex

@ -52,7 +52,7 @@
"mandatory": true "mandatory": true
}, },
"lang": {"description": {"english": "Language", "french": "Langue"}, "lang": {"description": {"english": "Language", "french": "Langue"},
"default": {"english": "english", "french": "french"} "default": {"english": "en", "french": "fr"}
}, },
"titleprefixwords": {"description": {"english": "Ignore some words in the beginning of song titles", "titleprefixwords": {"description": {"english": "Ignore some words in the beginning of song titles",
"french": "Ignore des mots dans le classement des chansons"}, "french": "Ignore des mots dans le classement des chansons"},
@ -80,11 +80,11 @@
(* block songbookpreambule *) (* block songbookpreambule *)
(( super() )) (( super() ))
(* for lang in _languages -*) (* for lang in _langs -*)
\PassOptionsToPackage{((lang))}{babel} \PassOptionsToPackage{(( lang2babel(lang) ))}{babel}
(* endfor *) (* endfor *)
\usepackage[((lang))]{babel} \usepackage[(( lang2babel(lang) ))]{babel}
\lang{((lang))} \lang{(( lang2babel(lang) ))}
\usepackage{graphicx} \usepackage{graphicx}
\graphicspath{ % \graphicspath{ %

23
patacrep/latex/__init__.py

@ -5,4 +5,27 @@ This module uses an LALR parser to try to parse LaTeX code. LaTeX language
will work on simple cases, but not on complex ones. will work on simple cases, but not on complex ones.
""" """
import logging
from collections import OrderedDict
from patacrep.latex.syntax import tex2plain, parse_song from patacrep.latex.syntax import tex2plain, parse_song
LOGGER = logging.getLogger(__name__)
BABEL_LANGUAGES = OrderedDict((
('fr', 'french'),
('en', 'english'),
('de', 'german'),
('es', 'spanish'),
('it', 'italian'),
('pt', 'portuguese'),
))
def lang2babel(lang):
"""Return the language used by babel, corresponding to the language code"""
try:
return BABEL_LANGUAGES[lang]
except KeyError:
available = ", ".join(BABEL_LANGUAGES.keys())
LOGGER.error('Unknown lang code: ' + lang + '. Supported: ' + available)
return 'english'

6
patacrep/latex/ast.py

@ -2,6 +2,8 @@
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
DEFAULT_LANGUAGE = "english"
class AST: class AST:
"""Base class for the tree.""" """Base class for the tree."""
# pylint: disable=no-init # pylint: disable=no-init
@ -16,7 +18,7 @@ class AST:
parsing. parsing.
""" """
cls.metadata = { cls.metadata = {
'@languages': set(), '@language': DEFAULT_LANGUAGE,
} }
class Expression(AST): class Expression(AST):
@ -44,7 +46,7 @@ class Command(AST):
self.optional = optional self.optional = optional
if name == r'\selectlanguage': if name == r'\selectlanguage':
self.metadata['@languages'] |= set(self.mandatory) self.metadata['@language'] = self.mandatory[0]
def __str__(self): def __str__(self):
if self.name in [r'\emph']: if self.name in [r'\emph']:

2
patacrep/songbook.py

@ -105,6 +105,8 @@ def main():
options = argument_parser(sys.argv[1:]) options = argument_parser(sys.argv[1:])
songbook_path = options.book[0] songbook_path = options.book[0]
if os.path.exists(songbook_path + ".sb") and not os.path.exists(songbook_path):
songbook_path += ".sb"
basename = os.path.basename(songbook_path)[:-3] basename = os.path.basename(songbook_path)[:-3]

6
patacrep/songs/__init__.py

@ -89,7 +89,7 @@ class Song:
"cached", "cached",
"data", "data",
"subpath", "subpath",
"languages", "lang",
"authors", "authors",
"_filehash", "_filehash",
"_version", "_version",
@ -131,6 +131,7 @@ class Song:
self.titles = [] self.titles = []
self.data = {} self.data = {}
self.cached = None self.cached = None
self.lang = None
self._parse(config) self._parse(config)
# Post processing of data # Post processing of data
@ -182,8 +183,7 @@ class Song:
- titles: the list of (raw) titles. This list will be processed to - titles: the list of (raw) titles. This list will be processed to
remove prefixes. remove prefixes.
- languages: the list of languages used in the song, as languages - lang: the main language of the song, as language code..
recognized by the LaTeX babel package.
- authors: the list of (raw) authors. This list will be processed to - authors: the list of (raw) authors. This list will be processed to
'clean' it (see function :func:`patacrep.authors.processauthors`). 'clean' it (see function :func:`patacrep.authors.processauthors`).
- data: song metadata. Used (among others) to sort the songs. - data: song metadata. Used (among others) to sort the songs.

6
patacrep/songs/chordpro/__init__.py

@ -10,6 +10,7 @@ from patacrep import encoding, files
from patacrep.songs import Song from patacrep.songs import Song
from patacrep.songs.chordpro.syntax import parse_song from patacrep.songs.chordpro.syntax import parse_song
from patacrep.templates import Renderer from patacrep.templates import Renderer
from patacrep.latex import lang2babel
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -25,7 +26,7 @@ class ChordproSong(Song):
song = parse_song(song.read(), self.fullpath) song = parse_song(song.read(), self.fullpath)
self.authors = song.authors self.authors = song.authors
self.titles = song.titles self.titles = song.titles
self.languages = song.get_data_argument('language', [self.config['lang']]) self.lang = song.get_data_argument('lang', self.config['lang'])
self.data = song.meta self.data = song.meta
self.cached = { self.cached = {
'song': song, 'song': song,
@ -33,7 +34,7 @@ class ChordproSong(Song):
def render(self, output=None, template="song"): # pylint: disable=arguments-differ def render(self, output=None, template="song"): # pylint: disable=arguments-differ
context = { context = {
'language': self.languages[0], 'lang': self.lang,
"titles": self.titles, "titles": self.titles,
"authors": self.authors, "authors": self.authors,
"metadata": self.data, "metadata": self.data,
@ -52,6 +53,7 @@ class ChordproSong(Song):
])) ]))
jinjaenv.filters['search_image'] = self.search_image jinjaenv.filters['search_image'] = self.search_image
jinjaenv.filters['search_partition'] = self.search_partition jinjaenv.filters['search_partition'] = self.search_partition
jinjaenv.filters['lang2babel'] = lang2babel
try: try:
return Renderer( return Renderer(

4
patacrep/songs/chordpro/ast.py

@ -31,6 +31,7 @@ DIRECTIVE_SHORTCUTS = {
"c": "comment", "c": "comment",
"gc": "guitar_comment", "gc": "guitar_comment",
"cover": "cov", "cover": "cov",
"language": "lang",
} }
def directive_name(text): def directive_name(text):
@ -181,7 +182,7 @@ class Song(AST):
- content: the song content, as a list of objects `foo` such that - content: the song content, as a list of objects `foo` such that
`foo.inline` is True. `foo.inline` is True.
- titles: The list of titles - titles: The list of titles
- language: The language (if set), None otherwise - lang: The language code (if set), None otherwise
- authors: The list of authors - authors: The list of authors
- meta: Every other metadata. - meta: Every other metadata.
""" """
@ -193,7 +194,6 @@ class Song(AST):
"artist": "add_author", "artist": "add_author",
"key": "add_key", "key": "add_key",
"define": "add_cumulative", "define": "add_cumulative",
"language": "add_cumulative",
} }
def __init__(self, filename): def __init__(self, filename):

4
patacrep/songs/chordpro/data/chordpro/song_header

@ -1,5 +1,5 @@
(* if language is defined -*) (* if lang is defined -*)
{language: (( language ))} {lang: (( lang ))}
(* endif *) (* endif *)
(* if metadata.columns is defined -*) (* if metadata.columns is defined -*)
{columns: (( metadata.columns ))} {columns: (( metadata.columns ))}

4
patacrep/songs/chordpro/data/html/song_header

@ -17,8 +17,8 @@
(* endif *) (* endif *)
(* endfor *) (* endfor *)
(* if language is defined -*) (* if lang is defined -*)
<span class="song-language">Language: (( language ))</span><br> <span class="song-language">Lang: (( lang ))</span><br>
(* endif *) (* endif *)
(* include 'content_metadata_cover' *) (* include 'content_metadata_cover' *)

4
patacrep/songs/chordpro/data/latex/song

@ -1,5 +1,5 @@
(* if language is defined -*) (* if lang is defined -*)
\selectlanguage{((language))} \selectlanguage{(( lang2babel(lang) ))}
(* endif *) (* endif *)
(*- if metadata.columns is defined *) (*- if metadata.columns is defined *)

20
patacrep/songs/latex/__init__.py

@ -8,7 +8,7 @@ will work on simple cases, but not on complex ones.
import os import os
from patacrep import files, encoding from patacrep import files, encoding
from patacrep.latex import parse_song from patacrep.latex import parse_song, BABEL_LANGUAGES
from patacrep.songs import Song from patacrep.songs import Song
class Latex2LatexSong(Song): class Latex2LatexSong(Song):
@ -16,13 +16,13 @@ class Latex2LatexSong(Song):
# pylint: disable=abstract-method # pylint: disable=abstract-method
def _parse(self, __config): def _parse(self, __config):
"""Parse content, and return the dictinory of song data.""" """Parse content, and return the dictionary of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song: with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
self.data = parse_song(song.read(), self.fullpath) self.data = parse_song(song.read(), self.fullpath)
self.titles = self.data['@titles'] self.titles = self.data['@titles']
del self.data['@titles'] del self.data['@titles']
self.languages = self.data['@languages'] self.set_lang(self.data['@language'])
del self.data['@languages'] del self.data['@language']
if "by" in self.data: if "by" in self.data:
self.authors = [self.data['by']] self.authors = [self.data['by']]
del self.data['by'] del self.data['by']
@ -40,6 +40,18 @@ class Latex2LatexSong(Song):
)) ))
return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path)) return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path))
def set_lang(self, language):
"""Set the language code"""
for lang, babel_language in BABEL_LANGUAGES.items():
if language == babel_language:
self.lang = lang
return
# Add a custom language to the babel dictionary (language is not officially supported)
custom_lang = '_' + language
BABEL_LANGUAGES[custom_lang] = language
self.lang = custom_lang
SONG_RENDERERS = { SONG_RENDERERS = {
"latex": { "latex": {
'is': Latex2LatexSong, 'is': Latex2LatexSong,

4
patacrep/templates.py

@ -9,6 +9,7 @@ import re
import json import json
from patacrep import errors, files from patacrep import errors, files
from patacrep.latex import lang2babel
import patacrep.encoding import patacrep.encoding
_LATEX_SUBS = ( _LATEX_SUBS = (
@ -84,6 +85,7 @@ class Renderer:
self.jinjaenv.trim_blocks = True self.jinjaenv.trim_blocks = True
self.jinjaenv.lstrip_blocks = True self.jinjaenv.lstrip_blocks = True
self.jinjaenv.globals["path2posix"] = files.path2posix self.jinjaenv.globals["path2posix"] = files.path2posix
self.jinjaenv.globals["lang2babel"] = lang2babel
self.template = self.jinjaenv.get_template(template) self.template = self.jinjaenv.get_template(template)
@ -153,7 +155,7 @@ class TexBookRenderer(Renderer):
variable = default["default"] variable = default["default"]
elif "en" in default: elif "en" in default:
variable = default["en"] variable = default["en"]
elif len(default > 0): elif len(default):
variable = default.popitem()[1] variable = default.popitem()[1]
else: else:
variable = None variable = None

2
test/test_chordpro/00.sgc

@ -1 +1 @@
{language: english} {lang: en}

2
test/test_chordpro/01.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A verse line A verse line

2
test/test_chordpro/02.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
{title: A directive} {title: A directive}

2
test/test_chordpro/03.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

2
test/test_chordpro/04.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_chorus} {start_of_chorus}
A one line chorus A one line chorus

2
test/test_chordpro/05.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_bridge} {start_of_bridge}
A one line bridge A one line bridge

2
test/test_chordpro/06.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

2
test/test_chordpro/07.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_tab} {start_of_tab}
A tab A tab

2
test/test_chordpro/08.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
A lot of new lines A lot of new lines

2
test/test_chordpro/09.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{title: and a directive} {title: and a directive}

2
test/test_chordpro/10.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A line[A] with a chord A line[A] with a chord

2
test/test_chordpro/11.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A line ending with a chord[A] A line ending with a chord[A]

2
test/test_chordpro/12.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
[A]A line starting with a chord [A]A line starting with a chord

2
test/test_chordpro/13.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_tab} {start_of_tab}
A table A table

2
test/test_chordpro/21.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A verse line A verse line

2
test/test_chordpro/22.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
{title: A directive} {title: A directive}

2
test/test_chordpro/23.sgc

@ -1 +1 @@
{language: english} {lang: en}

2
test/test_chordpro/24.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_chorus} {start_of_chorus}
A one line chorus A one line chorus

2
test/test_chordpro/25.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_bridge} {start_of_bridge}
A one line bridge A one line bridge

2
test/test_chordpro/26.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

2
test/test_chordpro/27.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_tab} {start_of_tab}
A tab A tab

2
test/test_chordpro/28.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
A lot of new lines A lot of new lines

2
test/test_chordpro/29.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{title: and a directive} {title: and a directive}

2
test/test_chordpro/author_names.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{title: Title} {title: Title}
{artist: The Beatles} {artist: The Beatles}
{artist: Oasis} {artist: Oasis}

2
test/test_chordpro/chords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
[A]Simple [A]Simple
[Bb]Bémol [Bb]Bémol

2
test/test_chordpro/customchords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{define: E4 base-fret 7 frets 0 1 3 3 x x} {define: E4 base-fret 7 frets 0 1 3 3 x x}
{define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} {define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -}
{define: E5/A* base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} {define: E5/A* base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -}

2
test/test_chordpro/greensleeves.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{columns: 2} {columns: 2}
{title: Greensleeves} {title: Greensleeves}
{title: Un autre sous-titre} {title: Un autre sous-titre}

2
test/test_chordpro/greensleeves.source

@ -1,4 +1,4 @@
{language : english} {lang : en}
{columns : 2} {columns : 2}
{subtitle : Un sous titre} {subtitle : Un sous titre}
{ title : Greensleeves} { title : Greensleeves}

2
test/test_chordpro/invalid_chord.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
This is invalid. This is invalid.

2
test/test_chordpro/invalid_customchord.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

1
test/test_chordpro/lang.sgc

@ -0,0 +1 @@
{lang: fr}

1
test/test_chordpro/lang.source

@ -0,0 +1 @@
{language: fr}

2
test/test_chordpro/metadata.sgc

@ -1,4 +1,4 @@
{language: french} {lang: fr}
{capo: Capo} {capo: Capo}
{title: Title} {title: Title}
{title: Subtitle1} {title: Subtitle1}

4
test/test_chordpro/metadata.source

@ -4,8 +4,8 @@
{subtitle: Subtitle4} {subtitle: Subtitle4}
{t: Subtitle2} {t: Subtitle2}
{st: Subtitle5} {st: Subtitle5}
{language: french} {lang: en}
{language: english} {lang: fr}
{by: Author1} {by: Author1}
{artist: Author2} {artist: Author2}
{album: Album} {album: Album}

2
test/test_chordpro/newline.html

@ -1,4 +1,4 @@
<span class="song-language">Language: english</span><br> <span class="song-language">Lang: en</span><br>

2
test/test_chordpro/newline.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
This is a verse This is a verse
With a new line With a new line

2
test/test_chordpro/nolyrics.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
A chorus [A]with lyrics A chorus [A]with lyrics
[Emaj3]maj et nombre [Emaj3]maj et nombre

2
test/test_chordpro/ukulelechords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{define: G frets 0 2 3 2} {define: G frets 0 2 3 2}
{define: D7 frets 2 2 2 3 fingers 1 1 1 2} {define: D7 frets 2 2 2 3 fingers 1 1 1 2}
{define: G frets 3 2 0 0 0 3} {define: G frets 3 2 0 0 0 3}

Loading…
Cancel
Save