jaseci 1.4.2.6__py3-none-any.whl → 2.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of jaseci might be problematic. Click here for more details.

Files changed (187) hide show
  1. jaseci/__init__.py +3 -37
  2. jaseci-2.0.0.dist-info/METADATA +65 -0
  3. jaseci-2.0.0.dist-info/RECORD +4 -0
  4. {jaseci-1.4.2.6.dist-info → jaseci-2.0.0.dist-info}/WHEEL +1 -2
  5. jaseci/VERSION +0 -1
  6. jaseci/cli_tools/__init__.py +0 -0
  7. jaseci/cli_tools/book_tools.py +0 -457
  8. jaseci/cli_tools/jsctl.py +0 -500
  9. jaseci/cli_tools/tests/__init__.py +0 -0
  10. jaseci/cli_tools/tests/test_jsctl.py +0 -556
  11. jaseci/extens/__init__.py +0 -0
  12. jaseci/extens/act_lib/__init__.py +0 -0
  13. jaseci/extens/act_lib/date.py +0 -118
  14. jaseci/extens/act_lib/elastic.py +0 -87
  15. jaseci/extens/act_lib/file.py +0 -77
  16. jaseci/extens/act_lib/file_handler.py +0 -190
  17. jaseci/extens/act_lib/internal.py +0 -19
  18. jaseci/extens/act_lib/jaseci.py +0 -62
  19. jaseci/extens/act_lib/mail.py +0 -10
  20. jaseci/extens/act_lib/maths.py +0 -168
  21. jaseci/extens/act_lib/net.py +0 -192
  22. jaseci/extens/act_lib/rand.py +0 -83
  23. jaseci/extens/act_lib/regex.py +0 -85
  24. jaseci/extens/act_lib/request.py +0 -170
  25. jaseci/extens/act_lib/std.py +0 -236
  26. jaseci/extens/act_lib/storage.py +0 -53
  27. jaseci/extens/act_lib/stripe.py +0 -338
  28. jaseci/extens/act_lib/task.py +0 -14
  29. jaseci/extens/act_lib/tests/__init__.py +0 -0
  30. jaseci/extens/act_lib/tests/std_test_code.py +0 -37
  31. jaseci/extens/act_lib/tests/test_date.py +0 -26
  32. jaseci/extens/act_lib/tests/test_elastic.py +0 -159
  33. jaseci/extens/act_lib/tests/test_file.py +0 -116
  34. jaseci/extens/act_lib/tests/test_file_lib.py +0 -40
  35. jaseci/extens/act_lib/tests/test_mail_lib.py +0 -33
  36. jaseci/extens/act_lib/tests/test_maths.py +0 -147
  37. jaseci/extens/act_lib/tests/test_net_lib.py +0 -62
  38. jaseci/extens/act_lib/tests/test_regex.py +0 -61
  39. jaseci/extens/act_lib/tests/test_std.py +0 -51
  40. jaseci/extens/act_lib/tests/test_std_lib.py +0 -36
  41. jaseci/extens/act_lib/tests/test_url.py +0 -32
  42. jaseci/extens/act_lib/tests/test_vector.py +0 -36
  43. jaseci/extens/act_lib/tests/test_webtool.py +0 -44
  44. jaseci/extens/act_lib/tests/test_zlib.py +0 -24
  45. jaseci/extens/act_lib/url.py +0 -80
  46. jaseci/extens/act_lib/vector.py +0 -158
  47. jaseci/extens/act_lib/webtool.py +0 -29
  48. jaseci/extens/act_lib/zip.py +0 -34
  49. jaseci/extens/api/__init__.py +0 -0
  50. jaseci/extens/api/actions_api.py +0 -171
  51. jaseci/extens/api/alias_api.py +0 -140
  52. jaseci/extens/api/architype_api.py +0 -197
  53. jaseci/extens/api/config_api.py +0 -129
  54. jaseci/extens/api/global_api.py +0 -85
  55. jaseci/extens/api/graph_api.py +0 -168
  56. jaseci/extens/api/health_api.py +0 -21
  57. jaseci/extens/api/interface.py +0 -269
  58. jaseci/extens/api/jac_api.py +0 -172
  59. jaseci/extens/api/jsorc_api.py +0 -317
  60. jaseci/extens/api/logger_api.py +0 -90
  61. jaseci/extens/api/master_api.py +0 -134
  62. jaseci/extens/api/object_api.py +0 -102
  63. jaseci/extens/api/prometheus_api.py +0 -75
  64. jaseci/extens/api/queue_api.py +0 -141
  65. jaseci/extens/api/sentinel_api.py +0 -271
  66. jaseci/extens/api/share_api.py +0 -64
  67. jaseci/extens/api/super_api.py +0 -65
  68. jaseci/extens/api/tests/__init__.py +0 -0
  69. jaseci/extens/api/tests/test_architype_api.py +0 -66
  70. jaseci/extens/api/tests/test_global_api.py +0 -179
  71. jaseci/extens/api/tests/test_graph_api.py +0 -64
  72. jaseci/extens/api/tests/test_logger_api.py +0 -43
  73. jaseci/extens/api/tests/test_object_api.py +0 -20
  74. jaseci/extens/api/tests/test_sentinel_api.py +0 -66
  75. jaseci/extens/api/tests/test_uncommon.py +0 -107
  76. jaseci/extens/api/tests/test_user_api.py +0 -32
  77. jaseci/extens/api/tests/test_walker_api.py +0 -316
  78. jaseci/extens/api/user_api.py +0 -166
  79. jaseci/extens/api/walker_api.py +0 -299
  80. jaseci/extens/api/webhook_api.py +0 -77
  81. jaseci/extens/svc/__init__.py +0 -0
  82. jaseci/extens/svc/elastic_svc.py +0 -366
  83. jaseci/extens/svc/kube_svc.py +0 -432
  84. jaseci/extens/svc/mail_svc.py +0 -156
  85. jaseci/extens/svc/prome_svc.py +0 -378
  86. jaseci/extens/svc/redis_svc.py +0 -63
  87. jaseci/extens/svc/storage_svc.py +0 -193
  88. jaseci/extens/svc/stripe_svc.py +0 -51
  89. jaseci/extens/svc/task_svc.py +0 -155
  90. jaseci/extens/svc/tasks.py +0 -302
  91. jaseci/jac/__init__.py +0 -0
  92. jaseci/jac/interpreter/__init__.py +0 -0
  93. jaseci/jac/interpreter/architype_interp.py +0 -219
  94. jaseci/jac/interpreter/interp.py +0 -1784
  95. jaseci/jac/interpreter/sentinel_interp.py +0 -260
  96. jaseci/jac/interpreter/tests/__init__.py +0 -0
  97. jaseci/jac/interpreter/tests/test_interp.py +0 -42
  98. jaseci/jac/interpreter/walker_interp.py +0 -249
  99. jaseci/jac/ir/__init__.py +0 -0
  100. jaseci/jac/ir/ast.py +0 -73
  101. jaseci/jac/ir/ast_builder.py +0 -249
  102. jaseci/jac/ir/jac_code.py +0 -151
  103. jaseci/jac/ir/passes/__init__.py +0 -6
  104. jaseci/jac/ir/passes/ast_prune_pass.py +0 -9
  105. jaseci/jac/ir/passes/codegen_pass.py +0 -244
  106. jaseci/jac/ir/passes/ir_pass.py +0 -29
  107. jaseci/jac/ir/passes/printer_pass.py +0 -23
  108. jaseci/jac/ir/passes/pt_prune_pass.py +0 -29
  109. jaseci/jac/ir/passes/schedule.py +0 -23
  110. jaseci/jac/ir/passes/stats_pass.py +0 -16
  111. jaseci/jac/jac.g4 +0 -450
  112. jaseci/jac/jac_parse/__init__.py +0 -0
  113. jaseci/jac/jac_parse/jacLexer.py +0 -809
  114. jaseci/jac/jac_parse/jacListener.py +0 -853
  115. jaseci/jac/jac_parse/jacParser.py +0 -9192
  116. jaseci/jac/jac_set.py +0 -120
  117. jaseci/jac/jsci_vm/__init__.py +0 -0
  118. jaseci/jac/jsci_vm/disasm.py +0 -94
  119. jaseci/jac/jsci_vm/inst_ptr.py +0 -31
  120. jaseci/jac/jsci_vm/machine.py +0 -188
  121. jaseci/jac/jsci_vm/op_codes.py +0 -82
  122. jaseci/jac/jsci_vm/tests/__init__.py +0 -0
  123. jaseci/jac/jsci_vm/tests/test_codegen.py +0 -31
  124. jaseci/jac/machine/__init__.py +0 -0
  125. jaseci/jac/machine/jac_scope.py +0 -86
  126. jaseci/jac/machine/jac_value.py +0 -227
  127. jaseci/jac/machine/machine_state.py +0 -386
  128. jaseci/jac/tests/__init__.py +0 -0
  129. jaseci/jac/tests/book_code.py +0 -624
  130. jaseci/jac/tests/test_book.py +0 -380
  131. jaseci/jac/tests/test_lang_14.py +0 -49
  132. jaseci/jsorc/__init__.py +0 -7
  133. jaseci/jsorc/jsorc.py +0 -642
  134. jaseci/jsorc/jsorc_settings.py +0 -211
  135. jaseci/jsorc/jsorc_utils.py +0 -298
  136. jaseci/jsorc/live_actions.py +0 -365
  137. jaseci/jsorc/manifests/__init__.py +0 -0
  138. jaseci/jsorc/manifests/database.yaml +0 -109
  139. jaseci/jsorc/manifests/elastic.yaml +0 -6029
  140. jaseci/jsorc/manifests/prometheus.yaml +0 -1383
  141. jaseci/jsorc/manifests/redis.yaml +0 -64
  142. jaseci/jsorc/memory.py +0 -258
  143. jaseci/jsorc/redis.py +0 -140
  144. jaseci/jsorc/remote_actions.py +0 -158
  145. jaseci/jsorc/tests/__init__.py +0 -0
  146. jaseci/jsorc/tests/test_actions.py +0 -542
  147. jaseci/jsorc/tests/test_jsorc.py +0 -112
  148. jaseci/prim/__init__.py +0 -0
  149. jaseci/prim/ability.py +0 -94
  150. jaseci/prim/architype.py +0 -90
  151. jaseci/prim/edge.py +0 -173
  152. jaseci/prim/element.py +0 -233
  153. jaseci/prim/graph.py +0 -27
  154. jaseci/prim/master.py +0 -67
  155. jaseci/prim/node.py +0 -533
  156. jaseci/prim/obj_mixins.py +0 -238
  157. jaseci/prim/sentinel.py +0 -282
  158. jaseci/prim/super_master.py +0 -31
  159. jaseci/prim/walker.py +0 -261
  160. jaseci/svc/__init__.py +0 -0
  161. jaseci/tests/__init__.py +0 -0
  162. jaseci/tests/infer.py +0 -39
  163. jaseci/tests/jac_test_code.py +0 -1293
  164. jaseci/tests/jac_test_progs.py +0 -774
  165. jaseci/tests/test_core.py +0 -153
  166. jaseci/tests/test_jac.py +0 -824
  167. jaseci/tests/test_node.py +0 -89
  168. jaseci/tests/test_progs.py +0 -702
  169. jaseci/tests/test_stack.py +0 -220
  170. jaseci/tests/test_stripe.py +0 -225
  171. jaseci/utils/__init__.py +0 -0
  172. jaseci/utils/actions/__init__.py +0 -0
  173. jaseci/utils/actions/actions_manager.py +0 -254
  174. jaseci/utils/actions/actions_optimizer.py +0 -517
  175. jaseci/utils/actions/actions_state.py +0 -95
  176. jaseci/utils/file_handler.py +0 -171
  177. jaseci/utils/gprof2dot.py +0 -3786
  178. jaseci/utils/id_list.py +0 -169
  179. jaseci/utils/json_handler.py +0 -70
  180. jaseci/utils/log_utils.py +0 -57
  181. jaseci/utils/test_core.py +0 -62
  182. jaseci/utils/utils.py +0 -387
  183. jaseci-1.4.2.6.dist-info/LICENSE +0 -21
  184. jaseci-1.4.2.6.dist-info/METADATA +0 -39
  185. jaseci-1.4.2.6.dist-info/RECORD +0 -185
  186. jaseci-1.4.2.6.dist-info/entry_points.txt +0 -3
  187. jaseci-1.4.2.6.dist-info/top_level.txt +0 -1
jaseci/utils/id_list.py DELETED
@@ -1,169 +0,0 @@
1
- """
2
- ID list class for Jaseci
3
-
4
- Generalized functions for managing '_ids' convention for lists of Jaseci
5
- objects
6
-
7
- parent_obj is the instance that the list belongs to
8
- """
9
-
10
- from jaseci.utils.utils import logger
11
-
12
-
13
- class IdList(list):
14
- """
15
- ID list class for tracking lists of objects in Jaseci
16
-
17
- ingest_list is a list of hex strings to convert to UUID and append.
18
- """
19
-
20
- def __init__(self, parent_obj, auto_save=True, in_list=None):
21
- self.parent_obj = parent_obj
22
- self.cached_objects = []
23
- self.heal_list = []
24
- self.auto_save = auto_save
25
- if in_list:
26
- self.extend(in_list)
27
-
28
- def cache_reset(self):
29
- self.cached_objects = []
30
-
31
- def add_obj(
32
- self, obj, push_front=False, allow_dups=False, silent=False, bypass=False
33
- ):
34
- """Adds a obj obj to Jaseci object"""
35
- self.parent_obj.check_hooks_match(obj)
36
- if not allow_dups and obj.jid in self:
37
- if not silent:
38
- logger.warning(str(f"{obj} is already in {self.parent_obj}'s list"))
39
- else:
40
- self.cache_reset()
41
- if push_front:
42
- self.insert(0, obj.jid)
43
- else:
44
- self.append(obj.jid)
45
-
46
- if not bypass:
47
- if not obj.j_parent:
48
- obj.j_parent = self.parent_obj.jid
49
- self.save(obj)
50
- self.save()
51
-
52
- def add_obj_list(self, obj_list, push_front=False, allow_dups=False, silent=False):
53
- self.cache_reset()
54
- if push_front:
55
- obj_list.reverse()
56
- for i in obj_list:
57
- self.add_obj(i, push_front=push_front, allow_dups=allow_dups, silent=silent)
58
-
59
- def remove_obj(self, obj):
60
- """Remove a Jaseci obj from list"""
61
- self.cache_reset()
62
- self.remove(obj.jid)
63
- self.save()
64
-
65
- def heal(self):
66
- for i in self.heal_list:
67
- self.remove(i)
68
- if len(self.heal_list) and hasattr(self.parent_obj, "save"):
69
- self.save()
70
- self.heal_list = []
71
-
72
- def destroy_obj(self, obj):
73
- """Completely destroys a Jaseci obj obj by it's name"""
74
- self.remove_obj(obj)
75
- obj.destroy()
76
-
77
- def obj_for_id_not_exist_error(self, item_id):
78
- self.heal_list.append(item_id)
79
- my_name = "id_list"
80
- for k, v in self.parent_obj.__dict__.items():
81
- if id(v) == id(self):
82
- my_name = k
83
- return f"{item_id} not found in {my_name} of {self.parent_obj}!"
84
-
85
- def get_obj_by_name(self, name, kind=None, silent=False):
86
- """Returns a Jaseci obj obj by it's name"""
87
- ret = None
88
- for i in self:
89
- obj = self.parent_obj._h.get_obj(self.parent_obj._m_id, i)
90
- if not obj:
91
- logger.critical(self.obj_for_id_not_exist_error(i))
92
- continue
93
- if obj.name == name:
94
- if kind and obj.kind != kind:
95
- continue
96
- ret = obj
97
- break
98
- if not ret and not silent:
99
- logger.error(str(f"object for '{name}' not found in '{self.parent_obj}'!"))
100
- self.heal()
101
- return ret
102
-
103
- def has_obj_by_name(self, name, kind=None):
104
- """Returns whether a Jaseci obj exists by it's name"""
105
- return self.get_obj_by_name(name, kind, silent=True) is not None
106
-
107
- def remove_obj_by_name(self, name, kind=None):
108
- """Remove a Jaseci obj by it's name"""
109
- self.remove_obj(self.get_obj_by_name(name, kind))
110
-
111
- def destroy_obj_by_name(self, name, kind=None):
112
- """Destroy a Jaseci obj by it's name"""
113
- self.destroy_obj(self.get_obj_by_name(name, kind))
114
-
115
- def obj_list(self):
116
- """Return list of objects from ids"""
117
- if not len(self.cached_objects):
118
- for i in self:
119
- obj = self.parent_obj._h.get_obj(self.parent_obj._m_id, i)
120
- if not obj:
121
- logger.critical(self.obj_for_id_not_exist_error(i))
122
- else:
123
- self.cached_objects.append(obj)
124
- self.heal()
125
- return self.cached_objects.copy()
126
-
127
- def remove_all(self):
128
- """Remove a Jaseci obj obj by it's name"""
129
- for i in self.obj_list():
130
- self.remove_obj(i)
131
- if len(self):
132
- logger.critical(
133
- str(
134
- f"Remove all failed in id_list of {self.parent_obj} - "
135
- + f"still has {self}!"
136
- )
137
- )
138
-
139
- def destroy_all(self):
140
- """Remove a Jaseci obj obj by it's name"""
141
- for i in self.obj_list():
142
- self.destroy_obj(i)
143
- if len(self):
144
- logger.critical(
145
- str(
146
- f"Destroy all failed in id_list of {self.parent_obj} - "
147
- + f"still has {self}!"
148
- )
149
- )
150
-
151
- def first_obj(self):
152
- """Get first object in list"""
153
- if not self:
154
- logger.error(str(f"List in '{self.parent_obj}' is empty!"))
155
- return None
156
- return self.parent_obj._h.get_obj(self.parent_obj._m_id, self[0])
157
-
158
- def pop_first_obj(self):
159
- """Get first object in list"""
160
- ret = self.first_obj()
161
- if ret:
162
- self.remove_obj(ret)
163
- return ret
164
-
165
- def save(self, obj=None):
166
- if self.auto_save:
167
- self.parent_obj.save()
168
- if obj:
169
- obj.save()
@@ -1,70 +0,0 @@
1
- import json
2
- from json import JSONDecoder, JSONEncoder
3
-
4
- from jaseci.jsorc.jsorc import JsOrc
5
- from jaseci.utils.id_list import IdList
6
- from jaseci.utils.utils import logger
7
-
8
-
9
- class JaseciJsonEncoder(JSONEncoder):
10
- def default(self, obj):
11
- from jaseci.prim.element import Element
12
-
13
- if isinstance(obj, Element):
14
- return {"__mem_id__": obj.jid}
15
- else:
16
- return super().default(obj)
17
-
18
-
19
- class JaseciJsonDecoder(JSONDecoder):
20
- def __init__(self, *args, **kwargs):
21
- JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
22
-
23
- def object_hook(self, obj):
24
- if isinstance(obj, dict):
25
- if "__mem_id__" in obj:
26
- return self.convert(obj["__mem_id__"])
27
- else:
28
- for k in obj:
29
- self.transform(obj, k)
30
- elif isinstance(obj, (list, tuple)):
31
- for idx, k in enumerate(obj):
32
- self.transform(obj, idx)
33
- return obj
34
-
35
- def transform(self, obj, key):
36
- if isinstance(obj[key], dict):
37
- if "__mem_id__" in obj[key]:
38
- obj[key] = self.convert(obj[key]["__mem_id__"])
39
- else:
40
- for k in obj[key]:
41
- self.transform(obj[key], k)
42
- elif isinstance(obj[key], (list, tuple)):
43
- for idx, k in enumerate(obj[key]):
44
- self.transform(obj[key], idx)
45
-
46
- def convert(self, urn):
47
- return JsOrc.hook().get_obj_from_store(urn)
48
-
49
-
50
- def json_str_to_jsci_dict(input_str, parent_obj=None):
51
- """
52
- Helper function to convert JSON strings to dictionarys with _ids list
53
- conversions from hex to UUID
54
-
55
- ret_obj is the owning object for id_list objects
56
- """
57
-
58
- try:
59
- obj_fields = json.loads(input_str, cls=JaseciJsonDecoder)
60
- except ValueError:
61
- logger.error(str(f"Invalid jsci_obj string {input_str} on {parent_obj.jid}"))
62
- obj_fields = {}
63
- jsci_dict_normalize(obj_fields, parent_obj)
64
- return obj_fields
65
-
66
-
67
- def jsci_dict_normalize(obj_fields, parent_obj):
68
- for i in obj_fields.keys():
69
- if str(i).endswith("_ids") and isinstance(obj_fields[i], list):
70
- obj_fields[i] = IdList(parent_obj=parent_obj, in_list=obj_fields[i])
jaseci/utils/log_utils.py DELETED
@@ -1,57 +0,0 @@
1
- from io import StringIO
2
- import re
3
- from typing import List, Dict
4
-
5
-
6
- def match_date(line: str) -> str:
7
- """Extracts the date from a log line"""
8
- matched = re.match(r"\d\d\d\d-\d\d-\d\d\ \d\d:\d\d:\d\d", line)
9
- if matched:
10
- return matched.group()
11
-
12
- return None
13
-
14
-
15
- def match_level(line: str) -> str:
16
- """Extracts the level from a log line"""
17
- matched = re.search(r"-\s(ERROR|WARNING|INFO|DEBUG)\s-", line)
18
- if matched:
19
- [level] = matched.groups()
20
- return level
21
-
22
- return None
23
-
24
-
25
- def parse_logs(logs: List[str]) -> List[Dict[str, str]]:
26
- """Convert log lines into an object with log metadata"""
27
- result = []
28
- for log in logs:
29
- date = match_date(log)
30
- level = match_level(log)
31
- result.append({"date": date, "level": level, "log": log})
32
-
33
- return result
34
-
35
-
36
- class LimitedSlidingBuffer(StringIO):
37
- def __init__(self, buffer=None, max_size=5 * 1000 * 1000) -> None:
38
- """A string buffer that accepts a maximum length."""
39
- StringIO.__init__(self, buffer)
40
- self.current_size = 0
41
- self.max_size = max_size
42
-
43
- def write(self, str: str):
44
- self.current_size += len(str)
45
- ret = StringIO.write(self, str)
46
-
47
- if self.current_size > self.max_size:
48
- new_size = min(int(self.current_size / 2), self.max_size)
49
- keep_str = self.getvalue()
50
- if len(keep_str) > 1:
51
- keep_str = keep_str[len(keep_str) - new_size :]
52
- self.truncate(0)
53
- self.seek(0)
54
- StringIO.write(self, keep_str)
55
- self.current_size = new_size
56
-
57
- return ret
jaseci/utils/test_core.py DELETED
@@ -1,62 +0,0 @@
1
- import os
2
- import unittest
3
- from unittest import TestCase
4
-
5
- from jaseci.jsorc.jsorc import JsOrc
6
- from jaseci.utils.utils import TestCaseHelper
7
-
8
-
9
- class CoreTest(TestCaseHelper, TestCase):
10
- """Unit tests for Jac Core APIs"""
11
-
12
- fixture_src = __file__
13
-
14
- def setUp(self):
15
- super().setUp()
16
- self.smast = JsOrc.super_master()
17
- self.mast = JsOrc.master(h=self.smast._h)
18
-
19
- def tearDown(self):
20
- super().tearDown()
21
-
22
- def call(self, mast, pl):
23
- ret = mast.general_interface_to_api(api_name=pl[0], params=pl[1])
24
- return ret
25
-
26
- def load_jac(self, fn):
27
- with open(os.path.dirname(self.fixture_src) + "/fixtures/" + fn) as f:
28
- return f.read()
29
-
30
-
31
- def jac_testcase(jac_file: str, test_name: str, log_out: bool = False):
32
- """decorator for test cases"""
33
-
34
- def decorator(func):
35
- def wrapper(self):
36
- if log_out:
37
- self.logger_on()
38
- self.call(
39
- self.mast,
40
- ["sentinel_register", {"code": self.load_jac(jac_file)}],
41
- )
42
- ret = self.call(self.mast, ["walker_run", {"name": test_name}])
43
- func(self, ret)
44
- if log_out:
45
- self.logger_off()
46
-
47
- return wrapper
48
-
49
- return decorator
50
-
51
-
52
- def skip_without_redis(test):
53
- """
54
- Skip test if expected not to work without redis.
55
- """
56
-
57
- def skipper(*args, **kwargs):
58
- if not JsOrc.svc("redis").is_running():
59
- raise unittest.SkipTest("No Redis!")
60
- test(*args, **kwargs)
61
-
62
- return skipper