1#
2# test_simple_unit.py
3#
4# While these unit tests *do* perform low-level unit testing of the classes in pyparsing,
5# this testing module should also serve an instructional purpose, to clearly show simple passing
6# and failing parse cases of some basic pyparsing expressions.
7#
8# Copyright (c) 2018  Paul T. McGuire
9#
10import unittest
11import pyparsing as pp
12from collections import namedtuple
13from datetime import datetime
14
15ppt = pp.pyparsing_test
16TestParseResultsAsserts = ppt.TestParseResultsAsserts
17
18# Test spec data class for specifying simple pyparsing test cases
19PpTestSpec = namedtuple(
20    "PpTestSpec",
21    "desc expr text parse_fn " "expected_list expected_dict expected_fail_locn",
22)
23PpTestSpec.__new__.__defaults__ = ("", pp.Empty(), "", "parseString", None, None, None)
24
25
26class PyparsingExpressionTestCase(ppt.TestParseResultsAsserts, unittest.TestCase):
27    """
28    Base pyparsing testing class to parse various pyparsing expressions against
29    given text strings. Subclasses must define a class attribute 'tests' which
30    is a list of PpTestSpec instances.
31    """
32
33    tests = []
34
35    def runTest(self):
36        if self.__class__ is PyparsingExpressionTestCase:
37            return
38
39        for test_spec in self.tests:
40            # for each spec in the class's tests list, create a subtest
41            # that will either:
42            #  - parse the string with expected success, display the
43            #    results, and validate the returned ParseResults
44            #  - or parse the string with expected failure, display the
45            #    error message and mark the error location, and validate
46            #    the location against an expected value
47            with self.subTest(test_spec=test_spec):
48                test_spec.expr.streamline()
49                print(
50                    "\n{} - {}({})".format(
51                        test_spec.desc, type(test_spec.expr).__name__, test_spec.expr
52                    )
53                )
54
55                parsefn = getattr(test_spec.expr, test_spec.parse_fn)
56                if test_spec.expected_fail_locn is None:
57                    # expect success
58                    result = parsefn(test_spec.text)
59                    if test_spec.parse_fn == "parseString":
60                        print(result.dump())
61                        # compare results against given list and/or dict
62                        self.assertParseResultsEquals(
63                            result,
64                            expected_list=test_spec.expected_list,
65                            expected_dict=test_spec.expected_dict,
66                        )
67                    elif test_spec.parse_fn == "transformString":
68                        print(result)
69                        # compare results against given list and/or dict
70                        if test_spec.expected_list is not None:
71                            self.assertEqual([result], test_spec.expected_list)
72                    elif test_spec.parse_fn == "searchString":
73                        print(result)
74                        # compare results against given list and/or dict
75                        if test_spec.expected_list is not None:
76                            self.assertEqual([result], test_spec.expected_list)
77                else:
78                    # expect fail
79                    with self.assertRaisesParseException():
80                        try:
81                            parsefn(test_spec.text)
82                        except Exception as exc:
83                            print(pp.ParseException.explain(exc))
84                            self.assertEqual(exc.loc, test_spec.expected_fail_locn)
85                            raise
86
87
88# =========== TEST DEFINITIONS START HERE ==============
89
90
91class TestLiteral(PyparsingExpressionTestCase):
92    tests = [
93        PpTestSpec(
94            desc="Simple match",
95            expr=pp.Literal("xyz"),
96            text="xyz",
97            expected_list=["xyz"],
98        ),
99        PpTestSpec(
100            desc="Simple match after skipping whitespace",
101            expr=pp.Literal("xyz"),
102            text="  xyz",
103            expected_list=["xyz"],
104        ),
105        PpTestSpec(
106            desc="Simple fail - parse an empty string",
107            expr=pp.Literal("xyz"),
108            text="",
109            expected_fail_locn=0,
110        ),
111        PpTestSpec(
112            desc="Simple fail - parse a mismatching string",
113            expr=pp.Literal("xyz"),
114            text="xyu",
115            expected_fail_locn=0,
116        ),
117        PpTestSpec(
118            desc="Simple fail - parse a partially matching string",
119            expr=pp.Literal("xyz"),
120            text="xy",
121            expected_fail_locn=0,
122        ),
123        PpTestSpec(
124            desc="Fail - parse a partially matching string by matching individual letters",
125            expr=pp.Literal("x") + pp.Literal("y") + pp.Literal("z"),
126            text="xy",
127            expected_fail_locn=2,
128        ),
129    ]
130
131
132class TestCaselessLiteral(PyparsingExpressionTestCase):
133    tests = [
134        PpTestSpec(
135            desc="Match colors, converting to consistent case",
136            expr=(
137                pp.CaselessLiteral("RED")
138                | pp.CaselessLiteral("GREEN")
139                | pp.CaselessLiteral("BLUE")
140            )[...],
141            text="red Green BluE blue GREEN green rEd",
142            expected_list=["RED", "GREEN", "BLUE", "BLUE", "GREEN", "GREEN", "RED"],
143        ),
144    ]
145
146
147class TestWord(PyparsingExpressionTestCase):
148    tests = [
149        PpTestSpec(
150            desc="Simple Word match",
151            expr=pp.Word("xy"),
152            text="xxyxxyy",
153            expected_list=["xxyxxyy"],
154        ),
155        PpTestSpec(
156            desc="Simple Word match of two separate Words",
157            expr=pp.Word("x") + pp.Word("y"),
158            text="xxxxxyy",
159            expected_list=["xxxxx", "yy"],
160        ),
161        PpTestSpec(
162            desc="Simple Word match of two separate Words - implicitly skips whitespace",
163            expr=pp.Word("x") + pp.Word("y"),
164            text="xxxxx yy",
165            expected_list=["xxxxx", "yy"],
166        ),
167    ]
168
169
170class TestCombine(PyparsingExpressionTestCase):
171    tests = [
172        PpTestSpec(
173            desc="Parsing real numbers - fail, parsed numbers are in pieces",
174            expr=(pp.Word(pp.nums) + "." + pp.Word(pp.nums))[...],
175            text="1.2 2.3 3.1416 98.6",
176            expected_list=[
177                "1",
178                ".",
179                "2",
180                "2",
181                ".",
182                "3",
183                "3",
184                ".",
185                "1416",
186                "98",
187                ".",
188                "6",
189            ],
190        ),
191        PpTestSpec(
192            desc="Parsing real numbers - better, use Combine to combine multiple tokens into one",
193            expr=pp.Combine(pp.Word(pp.nums) + "." + pp.Word(pp.nums))[...],
194            text="1.2 2.3 3.1416 98.6",
195            expected_list=["1.2", "2.3", "3.1416", "98.6"],
196        ),
197    ]
198
199
200class TestRepetition(PyparsingExpressionTestCase):
201    tests = [
202        PpTestSpec(
203            desc="Match several words",
204            expr=(pp.Word("x") | pp.Word("y"))[...],
205            text="xxyxxyyxxyxyxxxy",
206            expected_list=["xx", "y", "xx", "yy", "xx", "y", "x", "y", "xxx", "y"],
207        ),
208        PpTestSpec(
209            desc="Match several words, skipping whitespace",
210            expr=(pp.Word("x") | pp.Word("y"))[...],
211            text="x x  y xxy yxx y xyx  xxy",
212            expected_list=[
213                "x",
214                "x",
215                "y",
216                "xx",
217                "y",
218                "y",
219                "xx",
220                "y",
221                "x",
222                "y",
223                "x",
224                "xx",
225                "y",
226            ],
227        ),
228        PpTestSpec(
229            desc="Match several words, skipping whitespace (old style)",
230            expr=pp.OneOrMore(pp.Word("x") | pp.Word("y")),
231            text="x x  y xxy yxx y xyx  xxy",
232            expected_list=[
233                "x",
234                "x",
235                "y",
236                "xx",
237                "y",
238                "y",
239                "xx",
240                "y",
241                "x",
242                "y",
243                "x",
244                "xx",
245                "y",
246            ],
247        ),
248        PpTestSpec(
249            desc="Match words and numbers - show use of results names to collect types of tokens",
250            expr=(pp.Word(pp.alphas)("alpha*") | pp.pyparsing_common.integer("int*"))[
251                ...
252            ],
253            text="sdlfj23084ksdfs08234kjsdlfkjd0934",
254            expected_list=["sdlfj", 23084, "ksdfs", 8234, "kjsdlfkjd", 934],
255            expected_dict={
256                "alpha": ["sdlfj", "ksdfs", "kjsdlfkjd"],
257                "int": [23084, 8234, 934],
258            },
259        ),
260        PpTestSpec(
261            desc="Using delimited_list (comma is the default delimiter)",
262            expr=pp.delimited_list(pp.Word(pp.alphas)),
263            text="xxyx,xy,y,xxyx,yxx, xy",
264            expected_list=["xxyx", "xy", "y", "xxyx", "yxx", "xy"],
265        ),
266        PpTestSpec(
267            desc="Using delimited_list (comma is the default delimiter) with trailing delimiter",
268            expr=pp.delimited_list(pp.Word(pp.alphas), allow_trailing_delim=True),
269            text="xxyx,xy,y,xxyx,yxx, xy,",
270            expected_list=["xxyx", "xy", "y", "xxyx", "yxx", "xy"],
271        ),
272        PpTestSpec(
273            desc="Using delimited_list, with ':' delimiter",
274            expr=pp.delimited_list(
275                pp.Word(pp.hexnums, exact=2), delim=":", combine=True
276            ),
277            text="0A:4B:73:21:FE:76",
278            expected_list=["0A:4B:73:21:FE:76"],
279        ),
280        PpTestSpec(
281            desc="Using delimited_list, with ':' delimiter",
282            expr=pp.delimited_list(
283                pp.Word(pp.hexnums, exact=2),
284                delim=":",
285                combine=True,
286                allow_trailing_delim=True,
287            ),
288            text="0A:4B:73:21:FE:76:",
289            expected_list=["0A:4B:73:21:FE:76:"],
290        ),
291    ]
292
293
294class TestResultsName(PyparsingExpressionTestCase):
295    tests = [
296        PpTestSpec(
297            desc="Match with results name",
298            expr=pp.Literal("xyz").set_results_name("value"),
299            text="xyz",
300            expected_dict={"value": "xyz"},
301            expected_list=["xyz"],
302        ),
303        PpTestSpec(
304            desc="Match with results name - using naming short-cut",
305            expr=pp.Literal("xyz")("value"),
306            text="xyz",
307            expected_dict={"value": "xyz"},
308            expected_list=["xyz"],
309        ),
310        PpTestSpec(
311            desc="Define multiple results names",
312            expr=pp.Word(pp.alphas, pp.alphanums)("key")
313            + "="
314            + pp.pyparsing_common.integer("value"),
315            text="range=5280",
316            expected_dict={"key": "range", "value": 5280},
317            expected_list=["range", "=", 5280],
318        ),
319    ]
320
321
322class TestGroups(PyparsingExpressionTestCase):
323    EQ = pp.Suppress("=")
324    tests = [
325        PpTestSpec(
326            desc="Define multiple results names in groups",
327            expr=pp.Group(
328                pp.Word(pp.alphas)("key") + EQ + pp.pyparsing_common.number("value")
329            )[...],
330            text="range=5280 long=-138.52 lat=46.91",
331            expected_list=[["range", 5280], ["long", -138.52], ["lat", 46.91]],
332        ),
333        PpTestSpec(
334            desc="Define multiple results names in groups - use Dict to define results names using parsed keys",
335            expr=pp.Dict(
336                pp.Group(pp.Word(pp.alphas) + EQ + pp.pyparsing_common.number)[...]
337            ),
338            text="range=5280 long=-138.52 lat=46.91",
339            expected_list=[["range", 5280], ["long", -138.52], ["lat", 46.91]],
340            expected_dict={"lat": 46.91, "long": -138.52, "range": 5280},
341        ),
342        PpTestSpec(
343            desc="Define multiple value types",
344            expr=pp.Dict(
345                pp.Group(
346                    pp.Word(pp.alphas)
347                    + EQ
348                    + (
349                        pp.pyparsing_common.number
350                        | pp.oneOf("True False")
351                        | pp.QuotedString("'")
352                    )
353                )[...]
354            ),
355            text="long=-122.47 lat=37.82 public=True name='Golden Gate Bridge'",
356            expected_list=[
357                ["long", -122.47],
358                ["lat", 37.82],
359                ["public", "True"],
360                ["name", "Golden Gate Bridge"],
361            ],
362            expected_dict={
363                "long": -122.47,
364                "lat": 37.82,
365                "public": "True",
366                "name": "Golden Gate Bridge",
367            },
368        ),
369    ]
370
371
372class TestParseAction(PyparsingExpressionTestCase):
373    tests = [
374        PpTestSpec(
375            desc="Parsing real numbers - use parse action to convert to float at parse time",
376            expr=pp.Combine(pp.Word(pp.nums) + "." + pp.Word(pp.nums)).add_parse_action(
377                lambda t: float(t[0])
378            )[...],
379            text="1.2 2.3 3.1416 98.6",
380            expected_list=[
381                1.2,
382                2.3,
383                3.1416,
384                98.6,
385            ],  # note, these are now floats, not strs
386        ),
387        PpTestSpec(
388            desc="Match with numeric string converted to int",
389            expr=pp.Word("0123456789").addParseAction(lambda t: int(t[0])),
390            text="12345",
391            expected_list=[12345],  # note - result is type int, not str
392        ),
393        PpTestSpec(
394            desc="Use two parse actions to convert numeric string, then convert to datetime",
395            expr=pp.Word(pp.nums).add_parse_action(
396                lambda t: int(t[0]), lambda t: datetime.utcfromtimestamp(t[0])
397            ),
398            text="1537415628",
399            expected_list=[datetime(2018, 9, 20, 3, 53, 48)],
400        ),
401        PpTestSpec(
402            desc="Use tokenMap for parse actions that operate on a single-length token",
403            expr=pp.Word(pp.nums).add_parse_action(
404                pp.token_map(int), pp.token_map(datetime.utcfromtimestamp)
405            ),
406            text="1537415628",
407            expected_list=[datetime(2018, 9, 20, 3, 53, 48)],
408        ),
409        PpTestSpec(
410            desc="Using a built-in function that takes a sequence of strs as a parse action",
411            expr=pp.Word(pp.hexnums, exact=2)[...].add_parse_action(":".join),
412            text="0A4B7321FE76",
413            expected_list=["0A:4B:73:21:FE:76"],
414        ),
415        PpTestSpec(
416            desc="Using a built-in function that takes a sequence of strs as a parse action",
417            expr=pp.Word(pp.hexnums, exact=2)[...].add_parse_action(sorted),
418            text="0A4B7321FE76",
419            expected_list=["0A", "21", "4B", "73", "76", "FE"],
420        ),
421    ]
422
423
424class TestResultsModifyingParseAction(PyparsingExpressionTestCase):
425    # do not make staticmethod
426    # @staticmethod
427    def compute_stats_parse_action(t):
428        # by the time this parse action is called, parsed numeric words
429        # have been converted to ints by a previous parse action, so
430        # they can be treated as ints
431        t["sum"] = sum(t)
432        t["ave"] = sum(t) / len(t)
433        t["min"] = min(t)
434        t["max"] = max(t)
435
436    tests = [
437        PpTestSpec(
438            desc="A parse action that adds new key-values",
439            expr=pp.pyparsing_common.integer[...].addParseAction(
440                compute_stats_parse_action
441            ),
442            text="27 1 14 22 89",
443            expected_list=[27, 1, 14, 22, 89],
444            expected_dict={"ave": 30.6, "max": 89, "min": 1, "sum": 153},
445        ),
446    ]
447
448
449class TestRegex(PyparsingExpressionTestCase):
450    tests = [
451        PpTestSpec(
452            desc="Parsing real numbers - using Regex instead of Combine",
453            expr=pp.Regex(r"\d+\.\d+").add_parse_action(lambda t: float(t[0]))[...],
454            text="1.2 2.3 3.1416 98.6",
455            expected_list=[
456                1.2,
457                2.3,
458                3.1416,
459                98.6,
460            ],  # note, these are now floats, not strs
461        ),
462    ]
463
464
465class TestParseCondition(PyparsingExpressionTestCase):
466    tests = [
467        PpTestSpec(
468            desc="Define a condition to only match numeric values that are multiples of 7",
469            expr=pp.Word(pp.nums).addCondition(lambda t: int(t[0]) % 7 == 0)[...],
470            text="14 35 77 12 28",
471            expected_list=["14", "35", "77"],
472        ),
473        PpTestSpec(
474            desc="Separate conversion to int and condition into separate parse action/conditions",
475            expr=pp.Word(pp.nums)
476            .add_parse_action(lambda t: int(t[0]))
477            .add_condition(lambda t: t[0] % 7 == 0)[...],
478            text="14 35 77 12 28",
479            expected_list=[14, 35, 77],
480        ),
481    ]
482
483
484class TestTransformStringUsingParseActions(PyparsingExpressionTestCase):
485    markup_convert_map = {
486        "*": "B",
487        "_": "U",
488        "/": "I",
489    }
490
491    # do not make staticmethod
492    # @staticmethod
493    def markup_convert(t):
494        htmltag = TestTransformStringUsingParseActions.markup_convert_map[
495            t.markup_symbol
496        ]
497        return "<{}>{}</{}>".format(htmltag, t.body, htmltag)
498
499    tests = [
500        PpTestSpec(
501            desc="Use transformString to convert simple markup to HTML",
502            expr=(
503                pp.one_of(markup_convert_map)("markup_symbol")
504                + "("
505                + pp.CharsNotIn(")")("body")
506                + ")"
507            ).add_parse_action(markup_convert),
508            text="Show in *(bold), _(underscore), or /(italic) type",
509            expected_list=[
510                "Show in <B>bold</B>, <U>underscore</U>, or <I>italic</I> type"
511            ],
512            parse_fn="transformString",
513        ),
514    ]
515
516
517class TestCommonHelperExpressions(PyparsingExpressionTestCase):
518    tests = [
519        PpTestSpec(
520            desc="A comma-delimited list of words",
521            expr=pp.delimited_list(pp.Word(pp.alphas)),
522            text="this, that, blah,foo,   bar",
523            expected_list=["this", "that", "blah", "foo", "bar"],
524        ),
525        PpTestSpec(
526            desc="A counted array of words",
527            expr=pp.Group(pp.counted_array(pp.Word("ab")))[...],
528            text="2 aaa bbb 0 3 abab bbaa abbab",
529            expected_list=[["aaa", "bbb"], [], ["abab", "bbaa", "abbab"]],
530        ),
531        PpTestSpec(
532            desc="skipping comments with ignore",
533            expr=(
534                pp.pyparsing_common.identifier("lhs")
535                + "="
536                + pp.pyparsing_common.fnumber("rhs")
537            ).ignore(pp.cpp_style_comment),
538            text="abc_100 = /* value to be tested */ 3.1416",
539            expected_list=["abc_100", "=", 3.1416],
540            expected_dict={"lhs": "abc_100", "rhs": 3.1416},
541        ),
542        PpTestSpec(
543            desc="some pre-defined expressions in pyparsing_common, and building a dotted identifier with delimted_list",
544            expr=(
545                pp.pyparsing_common.number("id_num")
546                + pp.delimitedList(pp.pyparsing_common.identifier, ".", combine=True)(
547                    "name"
548                )
549                + pp.pyparsing_common.ipv4_address("ip_address")
550            ),
551            text="1001 www.google.com 192.168.10.199",
552            expected_list=[1001, "www.google.com", "192.168.10.199"],
553            expected_dict={
554                "id_num": 1001,
555                "name": "www.google.com",
556                "ip_address": "192.168.10.199",
557            },
558        ),
559        PpTestSpec(
560            desc="using one_of (shortcut for Literal('a') | Literal('b') | Literal('c'))",
561            expr=pp.one_of("a b c")[...],
562            text="a b a b b a c c a b b",
563            expected_list=["a", "b", "a", "b", "b", "a", "c", "c", "a", "b", "b"],
564        ),
565        PpTestSpec(
566            desc="parsing nested parentheses",
567            expr=pp.nested_expr(),
568            text="(a b (c) d (e f g ()))",
569            expected_list=[["a", "b", ["c"], "d", ["e", "f", "g", []]]],
570        ),
571        PpTestSpec(
572            desc="parsing nested braces",
573            expr=(
574                pp.Keyword("if")
575                + pp.nested_expr()("condition")
576                + pp.nested_expr("{", "}")("body")
577            ),
578            text='if ((x == y) || !z) {printf("{}");}',
579            expected_list=[
580                "if",
581                [["x", "==", "y"], "||", "!z"],
582                ["printf(", '"{}"', ");"],
583            ],
584            expected_dict={
585                "condition": [[["x", "==", "y"], "||", "!z"]],
586                "body": [["printf(", '"{}"', ");"]],
587            },
588        ),
589    ]
590
591
592class TestWhitespaceMethods(PyparsingExpressionTestCase):
593    tests = [
594        # These test the single-element versions
595        PpTestSpec(
596            desc="The word foo",
597            expr=pp.Literal("foo").ignore_whitespace(),
598            text="      foo        ",
599            expected_list=["foo"],
600        ),
601        PpTestSpec(
602            desc="The word foo",
603            expr=pp.Literal("foo").leave_whitespace(),
604            text="      foo        ",
605            expected_fail_locn=0,
606        ),
607        PpTestSpec(
608            desc="The word foo",
609            expr=pp.Literal("foo").ignore_whitespace(),
610            text="foo",
611            expected_list=["foo"],
612        ),
613        PpTestSpec(
614            desc="The word foo",
615            expr=pp.Literal("foo").leave_whitespace(),
616            text="foo",
617            expected_list=["foo"],
618        ),
619        # These test the composite elements
620        PpTestSpec(
621            desc="If we recursively leave whitespace on the parent, this whitespace-dependent grammar will succeed, even if the children themselves skip whitespace",
622            expr=pp.And(
623                [
624                    pp.Literal(" foo").ignore_whitespace(),
625                    pp.Literal(" bar").ignore_whitespace(),
626                ]
627            ).leave_whitespace(recursive=True),
628            text=" foo bar",
629            expected_list=[" foo", " bar"],
630        ),
631        #
632        PpTestSpec(
633            desc="If we recursively ignore whitespace in our parsing, this whitespace-dependent grammar will fail, even if the children themselves keep whitespace",
634            expr=pp.And(
635                [
636                    pp.Literal(" foo").leave_whitespace(),
637                    pp.Literal(" bar").leave_whitespace(),
638                ]
639            ).ignore_whitespace(recursive=True),
640            text=" foo bar",
641            expected_fail_locn=1,
642        ),
643        PpTestSpec(
644            desc="If we leave whitespace on the parent, but it isn't recursive, this whitespace-dependent grammar will fail",
645            expr=pp.And(
646                [
647                    pp.Literal(" foo").ignore_whitespace(),
648                    pp.Literal(" bar").ignore_whitespace(),
649                ]
650            ).leave_whitespace(recursive=False),
651            text=" foo bar",
652            expected_fail_locn=5,
653        ),
654        # These test the Enhance classes
655        PpTestSpec(
656            desc="If we recursively leave whitespace on the parent, this whitespace-dependent grammar will succeed, even if the children themselves skip whitespace",
657            expr=pp.Optional(pp.Literal(" foo").ignore_whitespace()).leave_whitespace(
658                recursive=True
659            ),
660            text=" foo",
661            expected_list=[" foo"],
662        ),
663        #
664        PpTestSpec(
665            desc="If we ignore whitespace on the parent, but it isn't recursive, parsing will fail because we skip to the first character 'f' before the internal expr can see it",
666            expr=pp.Optional(pp.Literal(" foo").leave_whitespace()).ignore_whitespace(
667                recursive=True
668            ),
669            text=" foo",
670            expected_list=[],
671        ),
672        # PpTestSpec(
673        #     desc="If we leave whitespace on the parent, this whitespace-dependent grammar will succeed, even if the children themselves skip whitespace",
674        #     expr=pp.Optional(pp.Literal(" foo").ignoreWhitespace()).leaveWhitespace(
675        #         recursive=False
676        #     ),
677        #     text=" foo",
678        #     expected_list=[]
679        # ),
680    ]
681
682
683def _get_decl_line_no(cls):
684    import inspect
685
686    return inspect.getsourcelines(cls)[1]
687
688
689# get all test case classes defined in this module and sort them by decl line no
690test_case_classes = list(PyparsingExpressionTestCase.__subclasses__())
691test_case_classes.sort(key=_get_decl_line_no)
692
693# make into a suite and run it - this will run the tests in the same order
694# they are declared in this module
695#
696# runnable from setup.py using "python setup.py test -s simple_unit_tests.suite"
697#
698suite = unittest.TestSuite(cls() for cls in test_case_classes)
699
700
701# ============ MAIN ================
702
703if __name__ == "__main__":
704
705    result = unittest.TextTestRunner().run(suite)
706
707    exit(0 if result.wasSuccessful() else 1)
708