rapydscript-ns 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +96 -5
- package/README.md +161 -46
- package/TODO.md +2 -283
- package/language-service/index.js +4474 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -7
- package/src/baselib-builtins.pyj +77 -1
- package/src/baselib-containers.pyj +8 -4
- package/src/baselib-internal.pyj +30 -1
- package/src/baselib-str.pyj +8 -1
- package/src/lib/collections.pyj +1 -1
- package/src/lib/numpy.pyj +10 -10
- package/src/monaco-language-service/analyzer.js +131 -9
- package/src/monaco-language-service/builtins.js +12 -2
- package/src/monaco-language-service/completions.js +170 -1
- package/src/monaco-language-service/diagnostics.js +1 -1
- package/src/monaco-language-service/index.js +17 -0
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +20 -3
- package/src/output/codegen.pyj +1 -1
- package/src/output/functions.pyj +4 -16
- package/src/output/loops.pyj +0 -9
- package/src/output/modules.pyj +1 -4
- package/src/output/operators.pyj +14 -0
- package/src/output/stream.pyj +0 -13
- package/src/parse.pyj +17 -1
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +110 -23
- package/test/slice.pyj +105 -0
- package/test/str.pyj +25 -0
- package/test/unit/fixtures/fibonacci_expected.js +1 -1
- package/test/unit/index.js +119 -7
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +5 -5
- package/test/unit/language-service-completions.js +180 -0
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +35 -0
- package/test/unit/run-language-service.js +1 -0
- package/test/unit/web-repl.js +134 -0
- package/tools/build-language-service.js +2 -2
- package/tools/compiler.js +0 -24
- package/tools/export.js +3 -37
- package/web-repl/rapydscript.js +6 -40
- package/web-repl/language-service.js +0 -4187
package/README.md
CHANGED
|
@@ -108,9 +108,6 @@ Here are a few features of RapydScript:
|
|
|
108
108
|
- similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)
|
|
109
109
|
- it's self-hosting, that means the compiler is itself written in RapydScript and compiles into JavaScript
|
|
110
110
|
|
|
111
|
-
Let's not waste any more time with the introductions, however. The best way to
|
|
112
|
-
learn a new language/framework is to dive in.
|
|
113
|
-
|
|
114
111
|
|
|
115
112
|
Installation
|
|
116
113
|
------------
|
|
@@ -357,17 +354,10 @@ math_ops = {
|
|
|
357
354
|
}
|
|
358
355
|
```
|
|
359
356
|
|
|
360
|
-
I'm sure you will agree that the above code is cleaner than declaring 5
|
|
361
|
-
temporary variables first and assigning them to the object literal keys after.
|
|
362
357
|
Note that the example puts the function header (def()) and content on the same
|
|
363
|
-
line
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
good rule of thumb (to keep your code clean) is if your function needs
|
|
367
|
-
semi-colons ask yourself whether you should be inlining, and if it needs more
|
|
368
|
-
than 2 semi-colons, the answer is probably no (note that you can also use
|
|
369
|
-
semi-colons as newline separators within functions that aren't inlined, as in
|
|
370
|
-
the example in the previous section).
|
|
358
|
+
line (function inlining). This is a feature of RapydScript that can be used
|
|
359
|
+
to make the code cleaner in cases like the example above. You can also use it
|
|
360
|
+
in longer functions by chaining statements together using `;`.
|
|
371
361
|
|
|
372
362
|
|
|
373
363
|
Lambda Expressions
|
|
@@ -543,7 +533,7 @@ $(element)\
|
|
|
543
533
|
.show()
|
|
544
534
|
```
|
|
545
535
|
|
|
546
|
-
|
|
536
|
+
This feature handles `do/while` loops as well:
|
|
547
537
|
|
|
548
538
|
```js
|
|
549
539
|
a = 0
|
|
@@ -646,7 +636,9 @@ into the names optional parameters you specified in the function definition.
|
|
|
646
636
|
|
|
647
637
|
Inferred Tuple Packing/Unpacking
|
|
648
638
|
--------------------------------
|
|
649
|
-
Like Python, RapydScript allows inferred tuple packing/unpacking and assignment.
|
|
639
|
+
Like Python, RapydScript allows inferred tuple packing/unpacking and assignment. For
|
|
640
|
+
example, if you wanted to swap two variables, the following is simpler than explicitly
|
|
641
|
+
declaring a temporary variable:
|
|
650
642
|
|
|
651
643
|
```py
|
|
652
644
|
a, b = b, a
|
|
@@ -766,14 +758,19 @@ Containers (lists/sets/dicts)
|
|
|
766
758
|
### Lists
|
|
767
759
|
|
|
768
760
|
Lists in RapydScript are almost identical to lists in Python, but are also
|
|
769
|
-
native JavaScript arrays. The
|
|
770
|
-
``
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
761
|
+
native JavaScript arrays. The ``sort()`` and ``pop()`` methods behave exactly
|
|
762
|
+
as in Python: ``sort()`` performs a numeric sort (in-place, with optional ``key``
|
|
763
|
+
and ``reverse`` arguments) and ``pop()`` performs a bounds-checked pop (raises
|
|
764
|
+
``IndexError`` for out-of-bounds indices). If you need the native JavaScript
|
|
765
|
+
behavior for interop with external JS libraries, use ``jssort()`` (lexicographic
|
|
766
|
+
sort) and ``jspop()`` (no bounds check, always pops the last element). The old
|
|
767
|
+
``pysort()`` and ``pypop()`` names are kept as backward-compatible aliases.
|
|
768
|
+
|
|
769
|
+
Note that even list literals in RapydScript create Python-like list objects,
|
|
770
|
+
and you can also use the builtin ``list()`` function to create lists from other
|
|
771
|
+
iterable objects, just as you would in Python. You can create a RapydScript
|
|
772
|
+
list from a plain native JavaScript array by using the ``list_wrap()`` function,
|
|
773
|
+
like this:
|
|
777
774
|
|
|
778
775
|
```py
|
|
779
776
|
a = v'[1, 2]'
|
|
@@ -781,6 +778,29 @@ pya = list_wrap(a)
|
|
|
781
778
|
# Now pya is a python like list object that satisfies pya === a
|
|
782
779
|
```
|
|
783
780
|
|
|
781
|
+
### List Concatenation
|
|
782
|
+
|
|
783
|
+
The `+` operator concatenates two lists and returns a new list, exactly as in Python:
|
|
784
|
+
|
|
785
|
+
```py
|
|
786
|
+
a = [1, 2]
|
|
787
|
+
b = [3, 4]
|
|
788
|
+
c = a + b # [1, 2, 3, 4] — a and b are unchanged
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
The `+=` operator extends a list in-place (the original list object is mutated):
|
|
792
|
+
|
|
793
|
+
```py
|
|
794
|
+
a = [1, 2]
|
|
795
|
+
ref = a # ref and a point to the same list
|
|
796
|
+
a += [3, 4] # mutates a in-place
|
|
797
|
+
print(ref) # [1, 2, 3, 4] — ref sees the update
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
No special flag is required. The `+` operator compiles to a lightweight helper
|
|
801
|
+
(`ρσ_list_add`) that uses `Array.concat` for lists and falls back to native JS
|
|
802
|
+
`+` for numbers and strings.
|
|
803
|
+
|
|
784
804
|
### Sets
|
|
785
805
|
|
|
786
806
|
Sets in RapydScript are identical to those in python. You can create them using
|
|
@@ -1346,6 +1366,20 @@ str.format('{0:02d} {n}', 1, n=2) == '01 2'
|
|
|
1346
1366
|
...
|
|
1347
1367
|
```
|
|
1348
1368
|
|
|
1369
|
+
The `format(value[, spec])` builtin is also supported. It applies the Python
|
|
1370
|
+
format-spec mini-language to a single value — the same mini-language that
|
|
1371
|
+
follows `:` in f-strings and `str.format()` fields:
|
|
1372
|
+
|
|
1373
|
+
```py
|
|
1374
|
+
format(42, '08b') # '00101010' — zero-padded binary
|
|
1375
|
+
format(3.14159, '.2f') # '3.14' — fixed-point
|
|
1376
|
+
format('hi', '>10') # ' hi' — right-aligned in 10-char field
|
|
1377
|
+
format(42) # '42' — no spec: same as str(42)
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
Objects with a `__format__` method are dispatched to it, matching Python's
|
|
1381
|
+
protocol exactly.
|
|
1382
|
+
|
|
1349
1383
|
String predicate methods are also available:
|
|
1350
1384
|
|
|
1351
1385
|
```py
|
|
@@ -1697,7 +1731,7 @@ E.a(onclick=def():
|
|
|
1697
1731
|
|
|
1698
1732
|
Classes
|
|
1699
1733
|
-------
|
|
1700
|
-
|
|
1734
|
+
JavaScript is not known for having excellent class implementation - but RapydScript improves on that. Imagine we want a special text field that takes in a user color string and changes color based on it. Let's create such field via a class:
|
|
1701
1735
|
|
|
1702
1736
|
```js
|
|
1703
1737
|
class ColorfulTextField:
|
|
@@ -1880,6 +1914,65 @@ print(Counter.get_count()) # 2
|
|
|
1880
1914
|
|
|
1881
1915
|
The `@classmethod` decorator compiles to a method placed directly on the class (not its prototype), with `cls` mapped to `this`. A prototype delegation shim is also generated so instance calls work correctly.
|
|
1882
1916
|
|
|
1917
|
+
### Nested Classes
|
|
1918
|
+
|
|
1919
|
+
A class may be defined inside another class. The nested class becomes an attribute of the outer class (accessible as `Outer.Inner`) and is also reachable via instances (`self.Inner` inside methods). This mirrors Python semantics exactly.
|
|
1920
|
+
|
|
1921
|
+
```py
|
|
1922
|
+
class Molecule:
|
|
1923
|
+
class Atom:
|
|
1924
|
+
def __init__(self, element):
|
|
1925
|
+
self.element = element
|
|
1926
|
+
def __repr__(self):
|
|
1927
|
+
return 'Atom(' + self.element + ')'
|
|
1928
|
+
|
|
1929
|
+
def __init__(self, elements):
|
|
1930
|
+
self.structure = []
|
|
1931
|
+
for e in elements:
|
|
1932
|
+
self.structure.push(Molecule.Atom(e))
|
|
1933
|
+
|
|
1934
|
+
water = Molecule(['H', 'H', 'O'])
|
|
1935
|
+
print(len(water.structure)) # 3
|
|
1936
|
+
print(water.structure[0].element) # H
|
|
1937
|
+
print(isinstance(water.structure[0], Molecule.Atom)) # True
|
|
1938
|
+
```
|
|
1939
|
+
|
|
1940
|
+
The nested class is a full class in every respect — it can have its own methods, inherit from other classes, and contain further nested classes:
|
|
1941
|
+
|
|
1942
|
+
```py
|
|
1943
|
+
class Universe:
|
|
1944
|
+
class Galaxy:
|
|
1945
|
+
class Star:
|
|
1946
|
+
def __init__(self, name):
|
|
1947
|
+
self.name = name
|
|
1948
|
+
def __init__(self, star_name):
|
|
1949
|
+
self.star = Universe.Galaxy.Star(star_name)
|
|
1950
|
+
def __init__(self, star_name):
|
|
1951
|
+
self.galaxy = Universe.Galaxy(star_name)
|
|
1952
|
+
|
|
1953
|
+
u = Universe('Sol')
|
|
1954
|
+
print(u.galaxy.star.name) # Sol
|
|
1955
|
+
print(isinstance(u.galaxy.star, Universe.Galaxy.Star)) # True
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
A nested class may also inherit from classes defined in the outer scope:
|
|
1959
|
+
|
|
1960
|
+
```py
|
|
1961
|
+
class Animal:
|
|
1962
|
+
def __init__(self, sound):
|
|
1963
|
+
self.sound = sound
|
|
1964
|
+
|
|
1965
|
+
class Zoo:
|
|
1966
|
+
class Dog(Animal):
|
|
1967
|
+
def __init__(self):
|
|
1968
|
+
Animal.__init__(self, 'woof')
|
|
1969
|
+
|
|
1970
|
+
fido = Zoo.Dog()
|
|
1971
|
+
print(fido.sound) # woof
|
|
1972
|
+
print(isinstance(fido, Animal)) # True
|
|
1973
|
+
print(isinstance(fido, Zoo.Dog)) # True
|
|
1974
|
+
```
|
|
1975
|
+
|
|
1883
1976
|
### External Classes
|
|
1884
1977
|
|
|
1885
1978
|
RapydScript will automatically detect classes declared within the same scope (as long as the declaration occurs before use), as well as classes properly imported into the module (each module making use of a certain class should explicitly import the module containing that class). RapydScript will also properly detect native JavaScript classes (String, Array, Date, etc.). Unfortunately, RapydScript has no way of detecting classes from third-party libraries. In those cases, you could use the `new` keyword every time you create an object from such class. Alternatively, you could mark the class as external.
|
|
@@ -2029,6 +2122,26 @@ next(it) # raises StopIteration
|
|
|
2029
2122
|
When the iterator is exhausted and no default is given, ``StopIteration``
|
|
2030
2123
|
is raised — matching standard Python behaviour.
|
|
2031
2124
|
|
|
2125
|
+
The two-argument form ``iter(callable, sentinel)`` repeatedly calls
|
|
2126
|
+
``callable`` (with no arguments) and yields each return value until it equals
|
|
2127
|
+
``sentinel``, at which point the iterator stops:
|
|
2128
|
+
|
|
2129
|
+
```python
|
|
2130
|
+
count = [0]
|
|
2131
|
+
def next_val():
|
|
2132
|
+
count[0] += 1
|
|
2133
|
+
return count[0]
|
|
2134
|
+
|
|
2135
|
+
list(iter(next_val, 4)) # [1, 2, 3]
|
|
2136
|
+
|
|
2137
|
+
for val in iter(next_val, 7):
|
|
2138
|
+
print(val) # 5, 6
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
The callable may be any function or object with a ``__call__`` method.
|
|
2142
|
+
Sentinel comparison uses strict equality (``===``), matching Python's
|
|
2143
|
+
``is``-then-``==`` semantics for the common case of plain values.
|
|
2144
|
+
|
|
2032
2145
|
Generators
|
|
2033
2146
|
------------
|
|
2034
2147
|
|
|
@@ -2039,18 +2152,19 @@ def f():
|
|
|
2039
2152
|
for i in range(3):
|
|
2040
2153
|
yield i
|
|
2041
2154
|
|
|
2042
|
-
[x for x in f()] == [1, 2
|
|
2155
|
+
[x for x in f()] == [0, 1, 2]
|
|
2043
2156
|
```
|
|
2044
2157
|
|
|
2045
2158
|
There is full support for generators including the Python 3, ```yield from```
|
|
2046
|
-
syntax.
|
|
2159
|
+
syntax.
|
|
2047
2160
|
|
|
2048
2161
|
Generators create JavaScript iterator objects. For differences between python
|
|
2049
|
-
and JavaScript iterators, see the section on iterators above.
|
|
2162
|
+
and JavaScript iterators, see the section on iterators above.
|
|
2050
2163
|
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2164
|
+
By default, generators are down-converted to ES 5 switch statements. Pass
|
|
2165
|
+
``--js-version 6`` to the compiler (or set ``js_version: 6`` in the embedded
|
|
2166
|
+
compiler options) to emit native ES 6 generator functions instead, which are
|
|
2167
|
+
smaller and faster.
|
|
2054
2168
|
|
|
2055
2169
|
Modules
|
|
2056
2170
|
-------
|
|
@@ -2191,7 +2305,7 @@ finally:
|
|
|
2191
2305
|
```
|
|
2192
2306
|
|
|
2193
2307
|
You can create your own Exception classes by inheriting from `Exception`, which
|
|
2194
|
-
is the JavaScript Error class, for more details on this, see
|
|
2308
|
+
is the JavaScript Error class, for more details on this, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error).
|
|
2195
2309
|
|
|
2196
2310
|
```py
|
|
2197
2311
|
class MyError(Exception):
|
|
@@ -2288,30 +2402,31 @@ Shadowing is preferred in most cases, since it can't accidentally damage outside
|
|
|
2288
2402
|
Available Libraries
|
|
2289
2403
|
-------------------
|
|
2290
2404
|
|
|
2291
|
-
One of Python's main strengths is the number of libraries available to the developer.
|
|
2405
|
+
One of Python's main strengths is the number of libraries available to the developer. The large number of readily-available JavaScript libraries will always outnumber community-made Rapydscript libraries. This is why RapydScript was designed with JS and DOM integration in mind from the beginning. For example, plugging in `lodash` in place of RapydScript's `stdlib` will work fine!
|
|
2292
2406
|
|
|
2293
|
-
|
|
2407
|
+
RapydScript's main strength is easy integration with JavaScript and DOM, and easy use of libraries that are already available. That doesn't mean, however, that pythonic libraries can't be written for RapydScript. Rapydscript comes with lightweight clones of several popular Python libraries, which you can find them in `src` directory.
|
|
2294
2408
|
|
|
2295
2409
|
math # replicates almost all of the functionality from Python's math library
|
|
2296
2410
|
re # replicates almost all of the functionality from Python's re library
|
|
2297
2411
|
random # replicates most of the functionality from Python's random library
|
|
2412
|
+
numpy # NumPy-compatible array library (ndarray, ufuncs, numpy.random, numpy.linalg)
|
|
2298
2413
|
elementmaker # easily construct DOM trees
|
|
2299
2414
|
aes # Implement AES symmetric encryption
|
|
2300
2415
|
encodings # Convert to/from UTF-8 bytearrays, base64 strings and native strings
|
|
2301
2416
|
gettext # Support for internationalization of your RapydScript app
|
|
2302
|
-
operator # a subset of
|
|
2417
|
+
operator # a subset of Python's operator module
|
|
2303
2418
|
functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
|
|
2304
2419
|
collections # namedtuple, deque, Counter, OrderedDict, defaultdict
|
|
2305
2420
|
itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
|
|
2306
2421
|
# groupby, islice, pairwise, starmap, takewhile, zip_longest,
|
|
2307
2422
|
# product, permutations, combinations, combinations_with_replacement
|
|
2308
2423
|
|
|
2309
|
-
For the most part, the logic implemented in these libraries functions identically to the Python versions.
|
|
2424
|
+
For the most part, the logic implemented in these libraries functions identically to the Python versions. I'd be happy to include more libraries, if other members of the community want them. However, unlike most other Python-to-JavaScript compilers, RapydScript doesn't need them to be complete since there are already tons of available JavaScript libraries that it can use natively.
|
|
2310
2425
|
|
|
2311
2426
|
Linter
|
|
2312
2427
|
---------
|
|
2313
2428
|
|
|
2314
|
-
The RapydScript compiler includes its own, built
|
|
2429
|
+
The RapydScript compiler includes its own, built-in linter. The linter is
|
|
2315
2430
|
modeled on pyflakes, it catches instances of unused/undefined variables,
|
|
2316
2431
|
functions, symbols, etc. While this sounds simple, it is surprisingly effective
|
|
2317
2432
|
in practice. To run the linter:
|
|
@@ -2391,13 +2506,12 @@ RapydScript will pick up any classes you declare yourself as well as native
|
|
|
2391
2506
|
JavaScript classes. It will not, however, pick up class-like objects created by
|
|
2392
2507
|
outside frameworks. There are two approaches for dealing with those. One is via
|
|
2393
2508
|
`@external` decorator, the other is via `new` operator when declaring such
|
|
2394
|
-
object.
|
|
2395
|
-
|
|
2396
|
-
may be more verbose:
|
|
2509
|
+
object. The `@external` decorator is recommended over the `new` operator for
|
|
2510
|
+
several reasons, even if it may be more verbose:
|
|
2397
2511
|
|
|
2398
2512
|
- `@external` decorator makes classes declared externally obvious to anyone looking at your code
|
|
2399
2513
|
- class declaration that uses `@external` decorator can be exported into a reusable module
|
|
2400
|
-
- developers are much more likely to forget a single instance of `new` operator when declaring an object than to forget an import
|
|
2514
|
+
- developers are much more likely to forget a single instance of `new` operator when declaring an object than to forget an import. the errors due to omitted `new` keyword are also likely to be more subtle and devious to debug
|
|
2401
2515
|
|
|
2402
2516
|
#### Embedding the RapydScript compiler in your webpage
|
|
2403
2517
|
|
|
@@ -2417,7 +2531,7 @@ HTML below for an example.
|
|
|
2417
2531
|
<head>
|
|
2418
2532
|
<meta charset="UTF-8">
|
|
2419
2533
|
<title>Test embedded RapydScript</title>
|
|
2420
|
-
<script charset="UTF-8" src="
|
|
2534
|
+
<script charset="UTF-8" src="rapydscript.js"></script>
|
|
2421
2535
|
<script>
|
|
2422
2536
|
var compiler = RapydScript.create_embedded_compiler();
|
|
2423
2537
|
var js = compiler.compile("def hello_world():\n a='RapydScript is cool!'\n print(a)\n alert(a)");
|
|
@@ -2511,9 +2625,10 @@ to an experienced Python developer. The most important such gotchas are listed
|
|
|
2511
2625
|
below:
|
|
2512
2626
|
|
|
2513
2627
|
- Truthiness in JavaScript is very different from Python. Empty lists and dicts
|
|
2514
|
-
are ``False`` in Python but ``True`` in JavaScript.
|
|
2515
|
-
|
|
2516
|
-
|
|
2628
|
+
are ``False`` in Python but ``True`` in JavaScript. You can opt in to full
|
|
2629
|
+
Python truthiness semantics (where empty containers are falsy and ``__bool__``
|
|
2630
|
+
is dispatched) with ``from __python__ import truthiness``. Without that flag,
|
|
2631
|
+
test the length explicitly instead of the container directly.
|
|
2517
2632
|
|
|
2518
2633
|
- Operators in JavaScript are very different from Python. ``1 + '1'`` would be
|
|
2519
2634
|
an error in Python, but results in ``'11'`` in JavaScript. Similarly, ``[1] +
|
|
@@ -2574,7 +2689,7 @@ npm run build:ls
|
|
|
2574
2689
|
node tools/build-language-service.js --out path/to/language-service.js
|
|
2575
2690
|
```
|
|
2576
2691
|
|
|
2577
|
-
The output is written to `
|
|
2692
|
+
The output is written to `language-service/index.js` by default.
|
|
2578
2693
|
|
|
2579
2694
|
### Basic setup
|
|
2580
2695
|
|
|
@@ -2769,7 +2884,7 @@ and assembled into the standard `mappings` field. The implementation lives in
|
|
|
2769
2884
|
|
|
2770
2885
|
```bash
|
|
2771
2886
|
node bin/web-repl-export web-repl # rebuilds web-repl/rapydscript.js
|
|
2772
|
-
node tools/build-language-service.js # rebuilds
|
|
2887
|
+
node tools/build-language-service.js # rebuilds language-service/index.js
|
|
2773
2888
|
```
|
|
2774
2889
|
|
|
2775
2890
|
### Running the tests
|