1# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"). You
4# may not use this file except in compliance with the License. A copy of
5# the License is located at
6#
7#     http://aws.amazon.com/apache2.0/
8#
9# or in the "license" file accompanying this file. This file is
10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11# ANY KIND, either express or implied. See the License for the specific
12# language governing permissions and limitations under the License.
13
14import logging
15
16logger = logging.getLogger('bcdocs')
17
18
19class BaseStyle(object):
20
21    def __init__(self, doc, indent_width=2):
22        self.doc = doc
23        self.indent_width = indent_width
24        self._indent = 0
25        self.keep_data = True
26
27    @property
28    def indentation(self):
29        return self._indent
30
31    @indentation.setter
32    def indentation(self, value):
33        self._indent = value
34
35    def new_paragraph(self):
36        return '\n%s' % self.spaces()
37
38    def indent(self):
39        self._indent += 1
40
41    def dedent(self):
42        if self._indent > 0:
43            self._indent -= 1
44
45    def spaces(self):
46        return ' ' * (self._indent * self.indent_width)
47
48    def bold(self, s):
49        return s
50
51    def ref(self, link, title=None):
52        return link
53
54    def h2(self, s):
55        return s
56
57    def h3(self, s):
58        return s
59
60    def underline(self, s):
61        return s
62
63    def italics(self, s):
64        return s
65
66
67class ReSTStyle(BaseStyle):
68
69    def __init__(self, doc, indent_width=2):
70        BaseStyle.__init__(self, doc, indent_width)
71        self.do_p = True
72        self.a_href = None
73
74    def new_paragraph(self):
75        if self.do_p:
76            self.doc.write('\n\n%s' % self.spaces())
77
78    def new_line(self):
79        if self.do_p:
80            self.doc.write('\n%s' % self.spaces())
81
82    def _start_inline(self, markup):
83        self.doc.write(markup)
84
85    def _end_inline(self, markup):
86        # Sometimes the HTML markup has whitespace between the end
87        # of the text inside the inline markup and the closing element
88        # (e.g. <b>foobar </b>).  This trailing space will cause
89        # problems in the ReST inline markup so we remove it here
90        # by popping the last item written off the stack, striping
91        # the whitespace and then pushing it back on the stack.
92        last_write = self.doc.pop_write()
93        self.doc.push_write(last_write.rstrip(' '))
94        self.doc.write(markup + ' ')
95
96    def start_bold(self, attrs=None):
97        self._start_inline('**')
98
99    def end_bold(self):
100        self._end_inline('**')
101
102    def start_b(self, attrs=None):
103        self.doc.do_translation = True
104        self.start_bold(attrs)
105
106    def end_b(self):
107        self.doc.do_translation = False
108        self.end_bold()
109
110    def bold(self, s):
111        if s:
112            self.start_bold()
113            self.doc.write(s)
114            self.end_bold()
115
116    def ref(self, title, link=None):
117        if link is None:
118            link = title
119        self.doc.write(':doc:`%s <%s>`' % (title, link))
120
121    def _heading(self, s, border_char):
122        border = border_char * len(s)
123        self.new_paragraph()
124        self.doc.write('%s\n%s\n%s' % (border, s, border))
125        self.new_paragraph()
126
127    def h1(self, s):
128        self._heading(s, '*')
129
130    def h2(self, s):
131        self._heading(s, '=')
132
133    def h3(self, s):
134        self._heading(s, '-')
135
136    def start_italics(self, attrs=None):
137        self._start_inline('*')
138
139    def end_italics(self):
140        self._end_inline('*')
141
142    def italics(self, s):
143        if s:
144            self.start_italics()
145            self.doc.write(s)
146            self.end_italics()
147
148    def start_p(self, attrs=None):
149        if self.do_p:
150            self.doc.write('\n\n%s' % self.spaces())
151
152    def end_p(self):
153        if self.do_p:
154            self.doc.write('\n\n')
155
156    def start_code(self, attrs=None):
157        self.doc.do_translation = True
158        self._start_inline('``')
159
160    def end_code(self):
161        self.doc.do_translation = False
162        self._end_inline('``')
163
164    def code(self, s):
165        if s:
166            self.start_code()
167            self.doc.write(s)
168            self.end_code()
169
170    def start_note(self, attrs=None):
171        self.new_paragraph()
172        self.doc.write('.. note::')
173        self.indent()
174        self.new_paragraph()
175
176    def end_note(self):
177        self.dedent()
178        self.new_paragraph()
179
180    def start_important(self, attrs=None):
181        self.new_paragraph()
182        self.doc.write('.. warning::')
183        self.indent()
184        self.new_paragraph()
185
186    def end_important(self):
187        self.dedent()
188        self.new_paragraph()
189
190    def start_a(self, attrs=None):
191        if attrs:
192            for attr_key, attr_value in attrs:
193                if attr_key == 'href':
194                    self.a_href = attr_value
195                    self.doc.write('`')
196        else:
197            # There are some model documentation that
198            # looks like this: <a>DescribeInstances</a>.
199            # In this case we just write out an empty
200            # string.
201            self.doc.write(' ')
202        self.doc.do_translation = True
203
204    def link_target_definition(self, refname, link):
205        self.doc.writeln('.. _%s: %s' % (refname, link))
206
207    def sphinx_reference_label(self, label, text=None):
208        if text is None:
209            text = label
210        if self.doc.target == 'html':
211            self.doc.write(':ref:`%s <%s>`' % (text, label))
212        else:
213            self.doc.write(text)
214
215    def end_a(self):
216        self.doc.do_translation = False
217        if self.a_href:
218            last_write = self.doc.pop_write()
219            last_write = last_write.rstrip(' ')
220            if last_write and last_write != '`':
221                if ':' in last_write:
222                    last_write = last_write.replace(':', r'\:')
223                self.doc.push_write(last_write)
224                self.doc.hrefs[last_write] = self.a_href
225                self.doc.write('`_')
226            elif last_write == '`':
227                # Look at start_a().  It will do a self.doc.write('`')
228                # which is the start of the link title.  If that is the
229                # case then there was no link text.  We should just
230                # use an inline link.  The syntax of this is
231                # `<http://url>`_
232                self.doc.push_write('`<%s>`_' % self.a_href)
233            else:
234                self.doc.push_write(self.a_href)
235                self.doc.hrefs[self.a_href] = self.a_href
236                self.doc.write('`_')
237            self.a_href = None
238        self.doc.write(' ')
239
240    def start_i(self, attrs=None):
241        self.doc.do_translation = True
242        self.start_italics()
243
244    def end_i(self):
245        self.doc.do_translation = False
246        self.end_italics()
247
248    def start_li(self, attrs=None):
249        self.new_line()
250        self.do_p = False
251        self.doc.write('* ')
252
253    def end_li(self):
254        self.do_p = True
255        self.new_line()
256
257    def li(self, s):
258        if s:
259            self.start_li()
260            self.doc.writeln(s)
261            self.end_li()
262
263    def start_ul(self, attrs=None):
264        self.new_paragraph()
265
266    def end_ul(self):
267        self.new_paragraph()
268
269    def start_ol(self, attrs=None):
270        # TODO: Need to control the bullets used for LI items
271        self.new_paragraph()
272
273    def end_ol(self):
274        self.new_paragraph()
275
276    def start_examples(self, attrs=None):
277        self.doc.keep_data = False
278
279    def end_examples(self):
280        self.doc.keep_data = True
281
282    def start_fullname(self, attrs=None):
283        self.doc.keep_data = False
284
285    def end_fullname(self):
286        self.doc.keep_data = True
287
288    def start_codeblock(self, attrs=None):
289        self.doc.write('::')
290        self.indent()
291        self.new_paragraph()
292
293    def end_codeblock(self):
294        self.dedent()
295        self.new_paragraph()
296
297    def codeblock(self, code):
298        """
299        Literal code blocks are introduced by ending a paragraph with
300        the special marker ::.  The literal block must be indented
301        (and, like all paragraphs, separated from the surrounding
302        ones by blank lines).
303        """
304        self.start_codeblock()
305        self.doc.writeln(code)
306        self.end_codeblock()
307
308    def toctree(self):
309        if self.doc.target == 'html':
310            self.doc.write('\n.. toctree::\n')
311            self.doc.write('  :maxdepth: 1\n')
312            self.doc.write('  :titlesonly:\n\n')
313        else:
314            self.start_ul()
315
316    def tocitem(self, item, file_name=None):
317        if self.doc.target == 'man':
318            self.li(item)
319        else:
320            if file_name:
321                self.doc.writeln('  %s' % file_name)
322            else:
323                self.doc.writeln('  %s' % item)
324
325    def hidden_toctree(self):
326        if self.doc.target == 'html':
327            self.doc.write('\n.. toctree::\n')
328            self.doc.write('  :maxdepth: 1\n')
329            self.doc.write('  :hidden:\n\n')
330
331    def hidden_tocitem(self, item):
332        if self.doc.target == 'html':
333            self.tocitem(item)
334
335    def table_of_contents(self, title=None, depth=None):
336        self.doc.write('.. contents:: ')
337        if title is not None:
338            self.doc.writeln(title)
339        if depth is not None:
340            self.doc.writeln('   :depth: %s' % depth)
341
342    def start_sphinx_py_class(self, class_name):
343        self.new_paragraph()
344        self.doc.write('.. py:class:: %s' % class_name)
345        self.indent()
346        self.new_paragraph()
347
348    def end_sphinx_py_class(self):
349        self.dedent()
350        self.new_paragraph()
351
352    def start_sphinx_py_method(self, method_name, parameters=None):
353        self.new_paragraph()
354        content = '.. py:method:: %s' % method_name
355        if parameters is not None:
356            content += '(%s)' % parameters
357        self.doc.write(content)
358        self.indent()
359        self.new_paragraph()
360
361    def end_sphinx_py_method(self):
362        self.dedent()
363        self.new_paragraph()
364
365    def write_py_doc_string(self, docstring):
366        docstring_lines = docstring.splitlines()
367        for docstring_line in docstring_lines:
368            self.doc.writeln(docstring_line)
369