1import os
2import re
3
4from common import TranslationJob, nice_mkdir, nice_path_join
5
6"""
7    Types of input lines
8"""
9TYPE_WHITESPACE = 1 # whitespace only
10TYPE_HEADER     = 2 # header (beginning with \b or \t)
11TYPE_BULLET     = 3 # bullet point
12TYPE_IMAGE      = 4 # image (beginning with \image)
13TYPE_CODE       = 5 # code (beginning with \s;)
14TYPE_PLAIN      = 6 # plain text
15
16class HelpTranslationJob(TranslationJob):
17    def __init__(self, **kwargs):
18        TranslationJob.__init__(self, **kwargs)
19        self.template_file = kwargs['template_file']
20        self.language_file = kwargs['language_file']
21        self.line_buffer = None
22
23    def process_file(self):
24        while True:
25            (paragraph, paragraph_type) = self.read_input_paragraph()
26            if not paragraph:
27                break
28
29            if paragraph_type == TYPE_WHITESPACE:
30                self.process_whitespace(paragraph[0])
31            elif paragraph_type == TYPE_HEADER:
32                self.process_header(paragraph[0])
33            elif paragraph_type == TYPE_BULLET:
34                self.process_bullet(paragraph[0])
35            elif paragraph_type == TYPE_IMAGE:
36                self.process_image(paragraph[0])
37            elif paragraph_type == TYPE_CODE:
38                self.process_code(paragraph)
39            elif paragraph_type == TYPE_PLAIN:
40                self.process_plain(paragraph)
41
42    """
43        Read one or more lines of input with same line type and return the list as paragraph
44        Exception is types which are processed as single lines, giving only paragraph with one line
45    """
46    def read_input_paragraph(self):
47        paragraph = None
48        paragraph_type = None
49        while True:
50            line = None
51            line_type = None
52            if self.line_buffer:
53                (line, line_type) = self.line_buffer
54                self.line_buffer = None
55            else:
56                line = self.read_input_line()
57                if line:
58                    line_type = self.check_line_type(line.text)
59
60            if not line:
61                break
62
63            if not paragraph_type:
64                paragraph_type = line_type
65
66            if paragraph_type == line_type:
67                if not paragraph:
68                    paragraph = []
69                paragraph.append(line)
70            else:
71                self.line_buffer = (line, line_type)
72                break
73
74            if line_type in [TYPE_WHITESPACE, TYPE_HEADER, TYPE_BULLET, TYPE_IMAGE]:
75                break
76
77        return (paragraph, paragraph_type)
78
79    def check_line_type(self, line):
80        if re.match(r'^\s*$', line) or re.match(r'^\\[nctr];$', line):
81            return TYPE_WHITESPACE
82        elif re.match(r'^\\[bt];', line):
83            return TYPE_HEADER
84        elif re.match(r'^\s*([0-9]\)|[o-])', line):
85            return TYPE_BULLET
86        elif re.match(r'^\\image.*;$', line):
87            return TYPE_IMAGE
88        elif re.match(r'^\\s;', line):
89            return TYPE_CODE
90        else:
91            return TYPE_PLAIN
92
93    def process_whitespace(self, line):
94        self.write_output_line(line.text)
95
96    def process_header(self, line):
97        match = re.match(r'^(\\[bt];)(.*)', line.text)
98        header_type = match.group(1)
99        header_text = match.group(2)
100        translated_header_text = self.translate_text(header_text, line.occurrence, header_type + ' header')
101        self.write_output_line(header_type + translated_header_text)
102
103    def process_bullet(self, line):
104        match = re.match(r'^(\s*)([0-9]\)|[o-])(\s*)(.*)', line.text)
105        spacing_before_bullet = match.group(1)
106        bullet_point          = match.group(2)
107        spacing_after_bullet  = match.group(3)
108        text                  = match.group(4)
109        translated_text = self.translate_text(
110            text, line.occurrence, "Bullet: '{0}'".format(bullet_point))
111        self.write_output_line(spacing_before_bullet + bullet_point + spacing_after_bullet + translated_text)
112
113    def process_image(self, line):
114        match = re.match(r'^(\\image )(.*)( \d* \d*;)$', line.text)
115        image_command = match.group(1)
116        image_source = match.group(2)
117        image_coords = match.group(3)
118        translated_image_source = self.translate_text(image_source, line.occurrence, 'Image filename')
119        self.write_output_line(image_command + translated_image_source + image_coords)
120
121    def process_code(self, paragraph):
122        text_lines = []
123        for line in paragraph:
124            match = re.match(r'^\\s;(.*)', line.text)
125            code_line = match.group(1)
126            text_lines.append(code_line)
127
128        joined_text_lines = '\n'.join(text_lines)
129        translated_text_lines = self.translate_text(joined_text_lines, paragraph[0].occurrence, 'Source code')
130        for line in translated_text_lines.split('\n'):
131            self.write_output_line(r'\s;' + line)
132
133    def process_plain(self, paragraph):
134        text_lines = []
135        for line in paragraph:
136            text_lines.append(line.text)
137
138        joined_text_lines = '\n'.join(text_lines)
139        translated_text_lines = self.translate_text(joined_text_lines, paragraph[0].occurrence, 'Plain text')
140        for line in translated_text_lines.split('\n'):
141            self.write_output_line(line)
142
143    def translate_text(self, text, occurrence, type_comment):
144        converted_text = convert_escape_syntax_to_tag_syntax(text)
145        self.template_file.insert_entry(converted_text, occurrence, type_comment)
146
147        if not self.language_file:
148            return text
149
150        translated_text = self.language_file.translate(converted_text)
151        return convert_tag_syntax_to_escape_syntax(translated_text)
152
153def convert_escape_syntax_to_tag_syntax(text):
154    # Replace \button $id; as pseudo xHTML <button $id/> tags
155    text = re.sub(r'\\(button|key) ([^;]*?);', r'<\1 \2/>', text)
156    # Put \const;Code\norm; sequences into pseudo-HTML <format const> tags
157    text = re.sub(r'\\(const|type|token|key);([^\\;]*?)\\norm;', r'<format \1>\2</format>', text)
158    # Transform CBot links \l;text\u target; into pseudo-HTML <a target>text</a>
159    text = re.sub(r'\\l;(.*?)\\u (.*?);', r'<a \2>\1</a>', text)
160    # Cleanup pseudo-html targets separated by \\ to have a single character |
161    text = re.sub(r'<a (.*?)\\(.*?)>', r'<a \1|\2>', text)
162    # Replace remnants of \const; \type; \token, \norm; or \key; as pseudo xHTML <const/> tags
163    text = re.sub(r'\\(const|type|token|norm|key);', r'<\1/>', text)
164    # Put \c;Code\n; sequences into pseudo-HTML <code> tags
165    text = re.sub(r'\\c;([^\\;]*?)\\n;', r'<code>\1</code>', text)
166    # Replace remnants of \s; \c; \b; or \n; as pseudo xHTML <s/> tags
167    text = re.sub(r'\\([scbn]);', r'<\1/>', text)
168    return text
169
170def convert_tag_syntax_to_escape_syntax(text):
171    # Invert the replace remnants of \s; \c; \b; or \n; as pseudo xHTML <s/> tags
172    text = re.sub(r'<([scbn])/>', r'\\\1;', text)
173    # Invert the put of \c;Code\n; sequences into pseudo-HTML <code> tags
174    text = re.sub(r'<code>([^\\;]*?)</code>', r'\\c;\1\\n;', text)
175    # Invert the replace remnants of \const; \type; \token or \norm; as pseudo xHTML <const/> tags
176    text = re.sub(r'<(const|type|token|norm|key)/>', r'\\\1;', text)
177    # Invert the cleanup of pseudo-html targets separated by \\ to have a single character |
178    text = re.sub(r'<a (.*?)\|(.*?)>', r'<a \1\\\2>', text)
179    # Invert the transform of CBot links \l;text\u target; into pseudo-HTML <a target>text</a>
180    text = re.sub(r'<a (.*?)>(.*?)</a>', r'\\l;\2\\u \1;', text)
181    # Invert the put \const;Code\norm; sequences into pseudo-HTML <format const> tags
182    text = re.sub(r'<format (const|type|token|key)>([^\\;]*?)</format>', r'\\\1;\2\\norm;', text)
183    # Invert the replace of \button $id; as pseudo xHTML <button $id/> tags
184    text = re.sub(r'<(button|key) (.*?)/>', r'\\\1 \2;', text)
185    return text
186
187"""
188    Create jobs for help translation
189
190    Assumes that input_dir has structure like so:
191        ${input_dir}/E/help_file1.txt
192        ...
193        ${input_dir}/E/help_fileN.txt
194
195    The output files will be saved in:
196        ${output_dir}/${language_char1}/${install_subdir}/help_file1.txt
197        ...
198        ${output_dir}/${language_charM}/${install_subdir}/help_fileN.txt
199"""
200def create_help_translation_jobs(input_dir, output_dir, install_subdir, template_file, language_files):
201    translation_jobs = []
202
203    e_dir = os.path.join(input_dir, 'E')
204    input_files = sorted(os.listdir(e_dir))
205
206    if not install_subdir:
207        install_subdir = ''
208
209    language_files_list = []
210    if len(language_files) > 0:
211        language_files_list = language_files
212    else:
213        # We need at least one dummy language file to create any jobs
214        language_files_list = [None]
215
216    for language_file in language_files_list:
217        output_translation_dir = None
218        if language_file:
219            output_translation_dir = nice_path_join(output_dir, language_file.language_char(), install_subdir)
220            nice_mkdir(output_translation_dir)
221
222        for input_file in input_files:
223            translation_jobs.append(HelpTranslationJob(
224                input_file    = os.path.join(e_dir, input_file),
225                output_file   = nice_path_join(output_translation_dir, input_file),
226                template_file = template_file,
227                language_file = language_file))
228
229    return translation_jobs
230