Show More
@@ -4,13 +4,13 b' Defines a variety of Pygments lexers for highlighting IPython code.' | |||
|
4 | 4 | |
|
5 | 5 | This includes: |
|
6 | 6 | |
|
7 |
IPython |
|
|
8 |
Lexer |
|
|
7 | IPython3Lexer | |
|
8 | Lexer for pure IPython (python + magic/shell commands) | |
|
9 | 9 | |
|
10 | 10 | IPythonPartialTracebackLexer, IPythonTracebackLexer |
|
11 | Supports 2.x and 3.x via keyword `python3`. The partial traceback | |
|
12 |
|
|
|
13 |
The full lexer combines the partial lexer with |
|
|
11 | The partial traceback lexer reads everything but the Python code | |
|
12 | appearing in a traceback. | |
|
13 | The full lexer combines the partial lexer with the IPython3Lexer. | |
|
14 | 14 | |
|
15 | 15 | IPythonConsoleLexer |
|
16 | 16 | A lexer for IPython console sessions, with support for tracebacks. |
@@ -35,10 +35,22 b' import re' | |||
|
35 | 35 | |
|
36 | 36 | # Third party |
|
37 | 37 | from pygments.lexers import ( |
|
38 | BashLexer, HtmlLexer, JavascriptLexer, RubyLexer, PerlLexer, PythonLexer, | |
|
39 | Python3Lexer, TexLexer) | |
|
38 | BashLexer, | |
|
39 | HtmlLexer, | |
|
40 | JavascriptLexer, | |
|
41 | RubyLexer, | |
|
42 | PerlLexer, | |
|
43 | Python3Lexer, | |
|
44 | TexLexer, | |
|
45 | ) | |
|
40 | 46 | from pygments.lexer import ( |
|
41 | Lexer, DelegatingLexer, RegexLexer, do_insertions, bygroups, using, | |
|
47 | Lexer, | |
|
48 | DelegatingLexer, | |
|
49 | RegexLexer, | |
|
50 | do_insertions, | |
|
51 | bygroups, | |
|
52 | using, | |
|
53 | inherit, | |
|
42 | 54 | ) |
|
43 | 55 | from pygments.token import ( |
|
44 | 56 | Generic, Keyword, Literal, Name, Operator, Other, Text, Error, |
@@ -49,80 +61,106 b' from pygments.util import get_bool_opt' | |||
|
49 | 61 | |
|
50 | 62 | line_re = re.compile('.*?\n') |
|
51 | 63 | |
|
52 | __all__ = ['build_ipy_lexer', 'IPython3Lexer', 'IPythonLexer', | |
|
53 | 'IPythonPartialTracebackLexer', 'IPythonTracebackLexer', | |
|
54 | 'IPythonConsoleLexer', 'IPyLexer'] | |
|
55 | ||
|
64 | __all__ = [ | |
|
65 | "IPython3Lexer", | |
|
66 | "IPythonPartialTracebackLexer", | |
|
67 | "IPythonTracebackLexer", | |
|
68 | "IPythonConsoleLexer", | |
|
69 | "IPyLexer", | |
|
70 | ] | |
|
56 | 71 | |
|
57 | def build_ipy_lexer(python3): | |
|
58 | """Builds IPython lexers depending on the value of `python3`. | |
|
59 | 72 | |
|
60 | The lexer inherits from an appropriate Python lexer and then adds | |
|
61 | information about IPython specific keywords (i.e. magic commands, | |
|
62 | shell commands, etc.) | |
|
73 | class IPython3Lexer(Python3Lexer): | |
|
74 | """IPython3 Lexer""" | |
|
63 | 75 | |
|
64 | Parameters | |
|
65 | ---------- | |
|
66 | python3 : bool | |
|
67 | If `True`, then build an IPython lexer from a Python 3 lexer. | |
|
76 | name = "IPython3" | |
|
77 | aliases = ["ipython3"] | |
|
68 | 78 | |
|
69 | """ | |
|
70 | # It would be nice to have a single IPython lexer class which takes | |
|
71 | # a boolean `python3`. But since there are two Python lexer classes, | |
|
72 | # we will also have two IPython lexer classes. | |
|
73 | if python3: | |
|
74 | PyLexer = Python3Lexer | |
|
75 | name = 'IPython3' | |
|
76 | aliases = ['ipython3'] | |
|
77 | doc = """IPython3 Lexer""" | |
|
78 | else: | |
|
79 | PyLexer = PythonLexer | |
|
80 | name = 'IPython' | |
|
81 | aliases = ['ipython2', 'ipython'] | |
|
82 | doc = """IPython Lexer""" | |
|
83 | ||
|
84 | ipython_tokens = [ | |
|
85 |
|
|
|
86 | (r'(?s)(\s*)(%%debug)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), | |
|
87 | (r'(?is)(\s*)(%%html)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(HtmlLexer))), | |
|
88 | (r'(?s)(\s*)(%%javascript)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), | |
|
89 |
|
|
|
90 | (r'(?s)(\s*)(%%latex)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(TexLexer))), | |
|
91 | (r'(?s)(\s*)(%%perl)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PerlLexer))), | |
|
92 | (r'(?s)(\s*)(%%prun)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), | |
|
93 |
|
|
|
94 | (r'(?s)(\s*)(%%python)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), | |
|
95 | (r'(?s)(\s*)(%%python2)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PythonLexer))), | |
|
96 | (r'(?s)(\s*)(%%python3)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(Python3Lexer))), | |
|
97 |
|
|
|
98 | (r'(?s)(\s*)(%%time)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), | |
|
99 | (r'(?s)(\s*)(%%timeit)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), | |
|
100 | (r'(?s)(\s*)(%%writefile)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), | |
|
101 |
|
|
|
102 | (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)), | |
|
103 | (r'(?s)(^\s*)(%%!)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(BashLexer))), | |
|
104 | (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)), | |
|
105 | (r"\b(\?\??)(\s*)$", bygroups(Operator, Text)), | |
|
106 | (r'(%)(sx|sc|system)(.*)(\n)', bygroups(Operator, Keyword, | |
|
107 | using(BashLexer), Text)), | |
|
108 | (r'(%)(\w+)(.*\n)', bygroups(Operator, Keyword, Text)), | |
|
109 |
( |
|
|
110 | (r'(!)(?!=)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), | |
|
111 | (r'^(\s*)(\?\??)(\s*%{0,2}[\w\.\*]*)', bygroups(Text, Operator, Text)), | |
|
112 | (r'(\s*%{0,2}[\w\.\*]*)(\?\??)(\s*)$', bygroups(Text, Operator, Text)), | |
|
113 | ] | |
|
114 | ||
|
115 | tokens = PyLexer.tokens.copy() | |
|
116 | tokens['root'] = ipython_tokens + tokens['root'] | |
|
117 | ||
|
118 | attrs = {'name': name, 'aliases': aliases, 'filenames': [], | |
|
119 | '__doc__': doc, 'tokens': tokens} | |
|
120 | ||
|
121 | return type(name, (PyLexer,), attrs) | |
|
122 | ||
|
123 | ||
|
124 | IPython3Lexer = build_ipy_lexer(python3=True) | |
|
125 | IPythonLexer = build_ipy_lexer(python3=False) | |
|
79 | tokens = { | |
|
80 | "root": [ | |
|
81 | ( | |
|
82 | r"(?s)(\s*)(%%capture)([^\n]*\n)(.*)", | |
|
83 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
84 | ), | |
|
85 | ( | |
|
86 | r"(?s)(\s*)(%%debug)([^\n]*\n)(.*)", | |
|
87 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
88 | ), | |
|
89 | ( | |
|
90 | r"(?is)(\s*)(%%html)([^\n]*\n)(.*)", | |
|
91 | bygroups(Text, Operator, Text, using(HtmlLexer)), | |
|
92 | ), | |
|
93 | ( | |
|
94 | r"(?s)(\s*)(%%javascript)([^\n]*\n)(.*)", | |
|
95 | bygroups(Text, Operator, Text, using(JavascriptLexer)), | |
|
96 | ), | |
|
97 | ( | |
|
98 | r"(?s)(\s*)(%%js)([^\n]*\n)(.*)", | |
|
99 | bygroups(Text, Operator, Text, using(JavascriptLexer)), | |
|
100 | ), | |
|
101 | ( | |
|
102 | r"(?s)(\s*)(%%latex)([^\n]*\n)(.*)", | |
|
103 | bygroups(Text, Operator, Text, using(TexLexer)), | |
|
104 | ), | |
|
105 | ( | |
|
106 | r"(?s)(\s*)(%%perl)([^\n]*\n)(.*)", | |
|
107 | bygroups(Text, Operator, Text, using(PerlLexer)), | |
|
108 | ), | |
|
109 | ( | |
|
110 | r"(?s)(\s*)(%%prun)([^\n]*\n)(.*)", | |
|
111 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
112 | ), | |
|
113 | ( | |
|
114 | r"(?s)(\s*)(%%pypy)([^\n]*\n)(.*)", | |
|
115 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
116 | ), | |
|
117 | ( | |
|
118 | r"(?s)(\s*)(%%python)([^\n]*\n)(.*)", | |
|
119 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
120 | ), | |
|
121 | ( | |
|
122 | r"(?s)(\s*)(%%python3)([^\n]*\n)(.*)", | |
|
123 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
124 | ), | |
|
125 | ( | |
|
126 | r"(?s)(\s*)(%%ruby)([^\n]*\n)(.*)", | |
|
127 | bygroups(Text, Operator, Text, using(RubyLexer)), | |
|
128 | ), | |
|
129 | ( | |
|
130 | r"(?s)(\s*)(%%time)([^\n]*\n)(.*)", | |
|
131 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
132 | ), | |
|
133 | ( | |
|
134 | r"(?s)(\s*)(%%timeit)([^\n]*\n)(.*)", | |
|
135 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
136 | ), | |
|
137 | ( | |
|
138 | r"(?s)(\s*)(%%writefile)([^\n]*\n)(.*)", | |
|
139 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
140 | ), | |
|
141 | ( | |
|
142 | r"(?s)(\s*)(%%file)([^\n]*\n)(.*)", | |
|
143 | bygroups(Text, Operator, Text, using(Python3Lexer)), | |
|
144 | ), | |
|
145 | (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)), | |
|
146 | ( | |
|
147 | r"(?s)(^\s*)(%%!)([^\n]*\n)(.*)", | |
|
148 | bygroups(Text, Operator, Text, using(BashLexer)), | |
|
149 | ), | |
|
150 | (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)), | |
|
151 | (r"\b(\?\??)(\s*)$", bygroups(Operator, Text)), | |
|
152 | ( | |
|
153 | r"(%)(sx|sc|system)(.*)(\n)", | |
|
154 | bygroups(Operator, Keyword, using(BashLexer), Text), | |
|
155 | ), | |
|
156 | (r"(%)(\w+)(.*\n)", bygroups(Operator, Keyword, Text)), | |
|
157 | (r"^(!!)(.+)(\n)", bygroups(Operator, using(BashLexer), Text)), | |
|
158 | (r"(!)(?!=)(.+)(\n)", bygroups(Operator, using(BashLexer), Text)), | |
|
159 | (r"^(\s*)(\?\??)(\s*%{0,2}[\w\.\*]*)", bygroups(Text, Operator, Text)), | |
|
160 | (r"(\s*%{0,2}[\w\.\*]*)(\?\??)(\s*)$", bygroups(Text, Operator, Text)), | |
|
161 | inherit, | |
|
162 | ] | |
|
163 | } | |
|
126 | 164 | |
|
127 | 165 | |
|
128 | 166 | class IPythonPartialTracebackLexer(RegexLexer): |
@@ -184,9 +222,9 b' class IPythonTracebackLexer(DelegatingLexer):' | |||
|
184 | 222 | this is the line which lists the File and line number. |
|
185 | 223 | |
|
186 | 224 | """ |
|
187 | # The lexer inherits from DelegatingLexer. The "root" lexer is an | |
|
188 | # appropriate IPython lexer, which depends on the value of the boolean | |
|
189 |
# |
|
|
225 | ||
|
226 | # The lexer inherits from DelegatingLexer. The "root" lexer is the | |
|
227 | # IPython3 lexer. First, we parse with the partial IPython traceback lexer. | |
|
190 | 228 | # Then, any code marked with the "Other" token is delegated to the root |
|
191 | 229 | # lexer. |
|
192 | 230 | # |
@@ -201,19 +239,9 b' class IPythonTracebackLexer(DelegatingLexer):' | |||
|
201 | 239 | # note we need a __init__ doc, as otherwise it inherits the doc from the super class |
|
202 | 240 | # which will fail the documentation build as it references section of the pygments docs that |
|
203 | 241 | # do not exists when building IPython's docs. |
|
204 | self.python3 = get_bool_opt(options, 'python3', False) | |
|
205 | if self.python3: | |
|
206 | self.aliases = ['ipython3tb'] | |
|
207 | else: | |
|
208 | self.aliases = ['ipython2tb', 'ipythontb'] | |
|
209 | 242 | |
|
210 | if self.python3: | |
|
211 | IPyLexer = IPython3Lexer | |
|
212 | else: | |
|
213 | IPyLexer = IPythonLexer | |
|
243 | super().__init__(IPython3Lexer, IPythonPartialTracebackLexer, **options) | |
|
214 | 244 | |
|
215 | DelegatingLexer.__init__(self, IPyLexer, | |
|
216 | IPythonPartialTracebackLexer, **options) | |
|
217 | 245 | |
|
218 | 246 | class IPythonConsoleLexer(Lexer): |
|
219 | 247 | """ |
@@ -255,8 +283,8 b' class IPythonConsoleLexer(Lexer):' | |||
|
255 | 283 | # continuation = ' .D.: ' |
|
256 | 284 | # template = 'Out[#]: ' |
|
257 | 285 | # |
|
258 |
# Where '#' is the 'prompt number' or 'execution count' and 'D' |
|
|
259 |
# D is a number of dots matching the width of the execution count |
|
|
286 | # Where '#' is the 'prompt number' or 'execution count' and 'D' | |
|
287 | # D is a number of dots matching the width of the execution count | |
|
260 | 288 | # |
|
261 | 289 | in1_regex = r'In \[[0-9]+\]: ' |
|
262 | 290 | in2_regex = r' \.\.+\.: ' |
@@ -270,9 +298,6 b' class IPythonConsoleLexer(Lexer):' | |||
|
270 | 298 | |
|
271 | 299 | Parameters |
|
272 | 300 | ---------- |
|
273 | python3 : bool | |
|
274 | If `True`, then the console inputs are parsed using a Python 3 | |
|
275 | lexer. Otherwise, they are parsed using a Python 2 lexer. | |
|
276 | 301 | in1_regex : RegexObject |
|
277 | 302 | The compiled regular expression used to detect the start |
|
278 | 303 | of inputs. Although the IPython configuration setting may have a |
@@ -288,11 +313,7 b' class IPythonConsoleLexer(Lexer):' | |||
|
288 | 313 | then the default output prompt is assumed. |
|
289 | 314 | |
|
290 | 315 | """ |
|
291 | self.python3 = get_bool_opt(options, 'python3', False) | |
|
292 | if self.python3: | |
|
293 | self.aliases = ['ipython3console'] | |
|
294 | else: | |
|
295 | self.aliases = ['ipython2console', 'ipythonconsole'] | |
|
316 | self.aliases = ["ipython3console"] | |
|
296 | 317 | |
|
297 | 318 | in1_regex = options.get('in1_regex', self.in1_regex) |
|
298 | 319 | in2_regex = options.get('in2_regex', self.in2_regex) |
@@ -318,15 +339,8 b' class IPythonConsoleLexer(Lexer):' | |||
|
318 | 339 | |
|
319 | 340 | Lexer.__init__(self, **options) |
|
320 | 341 | |
|
321 | if self.python3: | |
|
322 |
|
|
|
323 | tblexer = IPythonTracebackLexer | |
|
324 | else: | |
|
325 | pylexer = IPythonLexer | |
|
326 | tblexer = IPythonTracebackLexer | |
|
327 | ||
|
328 | self.pylexer = pylexer(**options) | |
|
329 | self.tblexer = tblexer(**options) | |
|
342 | self.pylexer = IPython3Lexer(**options) | |
|
343 | self.tblexer = IPythonTracebackLexer(**options) | |
|
330 | 344 | |
|
331 | 345 | self.reset() |
|
332 | 346 | |
@@ -512,20 +526,16 b' class IPyLexer(Lexer):' | |||
|
512 | 526 | def __init__(self, **options): |
|
513 | 527 | """ |
|
514 | 528 | Create a new IPyLexer instance which dispatch to either an |
|
515 |
IPythonC |
|
|
529 | IPythonConsoleLexer (if In prompts are present) or and IPython3Lexer (if | |
|
516 | 530 | In prompts are not present). |
|
517 | 531 | """ |
|
518 | 532 | # init docstring is necessary for docs not to fail to build do to parent |
|
519 | 533 | # docs referenceing a section in pygments docs. |
|
520 | self.python3 = get_bool_opt(options, 'python3', False) | |
|
521 | if self.python3: | |
|
522 | self.aliases = ['ipy3'] | |
|
523 | else: | |
|
524 | self.aliases = ['ipy2', 'ipy'] | |
|
534 | self.aliases = ["ipy3"] | |
|
525 | 535 | |
|
526 | 536 | Lexer.__init__(self, **options) |
|
527 | 537 | |
|
528 | self.IPythonLexer = IPythonLexer(**options) | |
|
538 | self.IPythonLexer = IPython3Lexer(**options) | |
|
529 | 539 | self.IPythonConsoleLexer = IPythonConsoleLexer(**options) |
|
530 | 540 | |
|
531 | 541 | def get_tokens_unprocessed(self, text): |
@@ -537,4 +547,3 b' class IPyLexer(Lexer):' | |||
|
537 | 547 | lex = self.IPythonLexer |
|
538 | 548 | for token in lex.get_tokens_unprocessed(text): |
|
539 | 549 | yield token |
|
540 |
@@ -16,10 +16,10 b' pyg214 = tuple(int(x) for x in pygments_version.split(".")[:2]) >= (2, 14)' | |||
|
16 | 16 | class TestLexers(TestCase): |
|
17 | 17 | """Collection of lexers tests""" |
|
18 | 18 | def setUp(self): |
|
19 | self.lexer = lexers.IPythonLexer() | |
|
19 | self.lexer = lexers.IPython3Lexer() | |
|
20 | 20 | self.bash_lexer = BashLexer() |
|
21 | 21 | |
|
22 | def testIPythonLexer(self): | |
|
22 | def testIPython3Lexer(self): | |
|
23 | 23 | fragment = '!echo $HOME\n' |
|
24 | 24 | bash_tokens = [ |
|
25 | 25 | (Token.Operator, '!'), |
@@ -4,12 +4,10 b' import pytest' | |||
|
4 | 4 | import pygments.lexers |
|
5 | 5 | import pygments.lexer |
|
6 | 6 | |
|
7 |
from IPython.lib.lexers import IPythonConsoleLexer, |
|
|
7 | from IPython.lib.lexers import IPythonConsoleLexer, IPython3Lexer | |
|
8 | 8 | |
|
9 | 9 | #: the human-readable names of the IPython lexers with ``entry_points`` |
|
10 | EXPECTED_LEXER_NAMES = [ | |
|
11 | cls.name for cls in [IPythonConsoleLexer, IPythonLexer, IPython3Lexer] | |
|
12 | ] | |
|
10 | EXPECTED_LEXER_NAMES = [cls.name for cls in [IPythonConsoleLexer, IPython3Lexer]] | |
|
13 | 11 | |
|
14 | 12 | |
|
15 | 13 | @pytest.fixture |
@@ -20,9 +20,5 b' def setup(app):' | |||
|
20 | 20 | # Alternatively, we could register the lexer with pygments instead. This would |
|
21 | 21 | # require using setuptools entrypoints: http://pygments.org/docs/plugins |
|
22 | 22 | |
|
23 | ipy2 = IPyLexer(python3=False) | |
|
24 | ipy3 = IPyLexer(python3=True) | |
|
25 | ||
|
26 | highlighting.lexers['ipython'] = ipy2 | |
|
27 | highlighting.lexers['ipython2'] = ipy2 | |
|
28 | highlighting.lexers['ipython3'] = ipy3 | |
|
23 | highlighting.lexers["ipython"] = IPyLexer() | |
|
24 | highlighting.lexers["ipython3"] = IPyLexer() |
@@ -9,22 +9,20 b' The IPython console lexer has been rewritten and now supports tracebacks' | |||
|
9 | 9 | and customized input/output prompts. An entire suite of lexers is now |
|
10 | 10 | available at :mod:`IPython.lib.lexers`. These include: |
|
11 | 11 | |
|
12 |
|
|
|
13 |
Lexer |
|
|
12 | IPython3Lexer | |
|
13 | Lexer for pure IPython (python 3 + magic/shell commands) | |
|
14 | 14 | |
|
15 | 15 | IPythonPartialTracebackLexer & IPythonTracebackLexer |
|
16 | Supports 2.x and 3.x via the keyword `python3`. The partial traceback | |
|
17 | lexer reads everything but the Python code appearing in a traceback. | |
|
18 | The full lexer combines the partial lexer with an IPython lexer. | |
|
16 | The partial traceback lexer reads everything but the Python code | |
|
17 | appearing in a traceback. The full lexer combines the partial lexer | |
|
18 | with the IPython3Lexer. | |
|
19 | 19 | |
|
20 | 20 | IPythonConsoleLexer |
|
21 | A lexer for IPython console sessions, with support for tracebacks. | |
|
22 | Supports 2.x and 3.x via the keyword `python3`. | |
|
21 | A lexer for python 3 IPython console sessions, with support for tracebacks. | |
|
23 | 22 | |
|
24 | 23 | IPyLexer |
|
25 | 24 | A friendly lexer which examines the first line of text and from it, |
|
26 | 25 | decides whether to use an IPython lexer or an IPython console lexer. |
|
27 | Supports 2.x and 3.x via the keyword `python3`. | |
|
28 | 26 | |
|
29 | 27 | Previously, the :class:`IPythonConsoleLexer` class was available at |
|
30 | 28 | :mod:`IPython.sphinxext.ipython_console_hightlight`. It was inserted |
@@ -145,7 +145,6 b' setup_args["entry_points"] = {' | |||
|
145 | 145 | "console_scripts": find_entry_points(), |
|
146 | 146 | "pygments.lexers": [ |
|
147 | 147 | "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer", |
|
148 | "ipython = IPython.lib.lexers:IPythonLexer", | |
|
149 | 148 | "ipython3 = IPython.lib.lexers:IPython3Lexer", |
|
150 | 149 | ], |
|
151 | 150 | } |
General Comments 0
You need to be logged in to leave comments.
Login now