bubbletea 0.1.2 → 0.1.4
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.
- checksums.yaml +4 -4
- data/bubbletea.gemspec +1 -0
- data/lib/bubbletea/commands.rb +14 -0
- data/lib/bubbletea/runner.rb +23 -0
- data/lib/bubbletea/version.rb +1 -1
- data/sig/bubbletea/commands.rbs +80 -0
- data/sig/bubbletea/messages.rbs +230 -0
- data/sig/bubbletea/model.rbs +55 -0
- data/sig/bubbletea/runner.rbs +56 -0
- data/sig/bubbletea/version.rbs +5 -0
- data/sig/bubbletea.rbs +6 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61f2c96627322e1bf260c2d72fe98a35cdb9f16231e7359a769b5836b3e09fc4
|
|
4
|
+
data.tar.gz: b0cd75bbe4cce25776bbf7dac4312a23955f58005aa32f7126ef0917483ca55c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b41e60273313f1801079e640d62e864ffbcebc28308d33c04ef83969c3df1375dc0a12b6f592d1bd57a54bf408d5aef95c09f7e1c8f5ba377461816305b36be6
|
|
7
|
+
data.tar.gz: 263ac8fc87768692f959be0cb803f10b55fc0d4164fd4543079c4b8c028c9f4d3ecbb8ae1ad17fd54501de6bfa06a5a7b1fab417543dcae0965b76ad81f09cd6
|
data/bubbletea.gemspec
CHANGED
data/lib/bubbletea/commands.rb
CHANGED
|
@@ -78,6 +78,16 @@ module Bubbletea
|
|
|
78
78
|
class SuspendCommand < Command
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
class ExecCommand < Command
|
|
82
|
+
attr_reader :callable, :message
|
|
83
|
+
|
|
84
|
+
def initialize(callable, message: nil)
|
|
85
|
+
super()
|
|
86
|
+
@callable = callable
|
|
87
|
+
@message = message
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
81
91
|
class << self
|
|
82
92
|
def quit
|
|
83
93
|
QuitCommand.new
|
|
@@ -122,5 +132,9 @@ module Bubbletea
|
|
|
122
132
|
def suspend
|
|
123
133
|
SuspendCommand.new
|
|
124
134
|
end
|
|
135
|
+
|
|
136
|
+
def exec(callable, message: nil)
|
|
137
|
+
ExecCommand.new(callable, message: message)
|
|
138
|
+
end
|
|
125
139
|
end
|
|
126
140
|
end
|
data/lib/bubbletea/runner.rb
CHANGED
|
@@ -233,6 +233,9 @@ module Bubbletea
|
|
|
233
233
|
when SuspendCommand
|
|
234
234
|
suspend_process
|
|
235
235
|
|
|
236
|
+
when ExecCommand
|
|
237
|
+
exec_process(command)
|
|
238
|
+
|
|
236
239
|
when Proc
|
|
237
240
|
Thread.new do
|
|
238
241
|
result = command.call
|
|
@@ -286,6 +289,9 @@ module Bubbletea
|
|
|
286
289
|
when SuspendCommand
|
|
287
290
|
suspend_process
|
|
288
291
|
|
|
292
|
+
when ExecCommand
|
|
293
|
+
exec_process(command)
|
|
294
|
+
|
|
289
295
|
when Proc
|
|
290
296
|
result = command.call
|
|
291
297
|
return unless result
|
|
@@ -360,6 +366,23 @@ module Bubbletea
|
|
|
360
366
|
handle_message(ResumeMessage.new)
|
|
361
367
|
end
|
|
362
368
|
|
|
369
|
+
def exec_process(command)
|
|
370
|
+
@program.disable_mouse if @options[:mouse_cell_motion] || @options[:mouse_all_motion]
|
|
371
|
+
@program.show_cursor
|
|
372
|
+
@program.stop_input_reader
|
|
373
|
+
@program.exit_raw_mode
|
|
374
|
+
|
|
375
|
+
command.callable.call
|
|
376
|
+
|
|
377
|
+
@program.enter_raw_mode
|
|
378
|
+
@program.hide_cursor
|
|
379
|
+
@program.start_input_reader
|
|
380
|
+
@program.enable_mouse_cell_motion if @options[:mouse_cell_motion]
|
|
381
|
+
@program.enable_mouse_all_motion if @options[:mouse_all_motion]
|
|
382
|
+
|
|
383
|
+
handle_message(command.message) if command.message
|
|
384
|
+
end
|
|
385
|
+
|
|
363
386
|
def render
|
|
364
387
|
return if @options[:without_renderer]
|
|
365
388
|
return unless @renderer_id
|
data/lib/bubbletea/version.rb
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Generated from lib/bubbletea/commands.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Bubbletea
|
|
4
|
+
class Command
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class QuitCommand < Command
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class BatchCommand < Command
|
|
11
|
+
attr_reader commands: untyped
|
|
12
|
+
|
|
13
|
+
def initialize: (untyped commands) -> untyped
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class TickCommand < Command
|
|
17
|
+
attr_reader duration: untyped
|
|
18
|
+
|
|
19
|
+
attr_reader callback: untyped
|
|
20
|
+
|
|
21
|
+
def initialize: (untyped duration) ?{ (?) -> untyped } -> untyped
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class SendMessage < Command
|
|
25
|
+
attr_reader message: untyped
|
|
26
|
+
|
|
27
|
+
attr_reader delay: untyped
|
|
28
|
+
|
|
29
|
+
def initialize: (untyped message, ?delay: untyped) -> untyped
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class SequenceCommand < Command
|
|
33
|
+
attr_reader commands: untyped
|
|
34
|
+
|
|
35
|
+
def initialize: (untyped commands) -> untyped
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class EnterAltScreenCommand < Command
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class ExitAltScreenCommand < Command
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class SetWindowTitleCommand < Command
|
|
45
|
+
attr_reader title: untyped
|
|
46
|
+
|
|
47
|
+
def initialize: (untyped title) -> untyped
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class PutsCommand < Command
|
|
51
|
+
attr_reader text: untyped
|
|
52
|
+
|
|
53
|
+
def initialize: (untyped text) -> untyped
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class SuspendCommand < Command
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.quit: () -> untyped
|
|
60
|
+
|
|
61
|
+
def self.batch: (*untyped commands) -> untyped
|
|
62
|
+
|
|
63
|
+
def self.tick: (untyped duration) ?{ (?) -> untyped } -> untyped
|
|
64
|
+
|
|
65
|
+
def self.send_message: (untyped message, ?delay: untyped) -> untyped
|
|
66
|
+
|
|
67
|
+
def self.sequence: (*untyped commands) -> untyped
|
|
68
|
+
|
|
69
|
+
def self.none: () -> untyped
|
|
70
|
+
|
|
71
|
+
def self.enter_alt_screen: () -> untyped
|
|
72
|
+
|
|
73
|
+
def self.exit_alt_screen: () -> untyped
|
|
74
|
+
|
|
75
|
+
def self.set_window_title: (untyped title) -> untyped
|
|
76
|
+
|
|
77
|
+
def self.puts: (untyped text) -> untyped
|
|
78
|
+
|
|
79
|
+
def self.suspend: () -> untyped
|
|
80
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Generated from lib/bubbletea/messages.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Bubbletea
|
|
4
|
+
class Message
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class KeyMessage < Message
|
|
8
|
+
KEY_NULL: ::Integer
|
|
9
|
+
|
|
10
|
+
KEY_CTRL_A: ::Integer
|
|
11
|
+
|
|
12
|
+
KEY_CTRL_B: ::Integer
|
|
13
|
+
|
|
14
|
+
KEY_CTRL_C: ::Integer
|
|
15
|
+
|
|
16
|
+
KEY_CTRL_D: ::Integer
|
|
17
|
+
|
|
18
|
+
KEY_CTRL_E: ::Integer
|
|
19
|
+
|
|
20
|
+
KEY_CTRL_F: ::Integer
|
|
21
|
+
|
|
22
|
+
KEY_CTRL_G: ::Integer
|
|
23
|
+
|
|
24
|
+
KEY_CTRL_H: ::Integer
|
|
25
|
+
|
|
26
|
+
KEY_TAB: ::Integer
|
|
27
|
+
|
|
28
|
+
KEY_CTRL_J: ::Integer
|
|
29
|
+
|
|
30
|
+
KEY_CTRL_K: ::Integer
|
|
31
|
+
|
|
32
|
+
KEY_CTRL_L: ::Integer
|
|
33
|
+
|
|
34
|
+
KEY_ENTER: ::Integer
|
|
35
|
+
|
|
36
|
+
KEY_CTRL_N: ::Integer
|
|
37
|
+
|
|
38
|
+
KEY_CTRL_O: ::Integer
|
|
39
|
+
|
|
40
|
+
KEY_CTRL_P: ::Integer
|
|
41
|
+
|
|
42
|
+
KEY_CTRL_Q: ::Integer
|
|
43
|
+
|
|
44
|
+
KEY_CTRL_R: ::Integer
|
|
45
|
+
|
|
46
|
+
KEY_CTRL_S: ::Integer
|
|
47
|
+
|
|
48
|
+
KEY_CTRL_T: ::Integer
|
|
49
|
+
|
|
50
|
+
KEY_CTRL_U: ::Integer
|
|
51
|
+
|
|
52
|
+
KEY_CTRL_V: ::Integer
|
|
53
|
+
|
|
54
|
+
KEY_CTRL_W: ::Integer
|
|
55
|
+
|
|
56
|
+
KEY_CTRL_X: ::Integer
|
|
57
|
+
|
|
58
|
+
KEY_CTRL_Y: ::Integer
|
|
59
|
+
|
|
60
|
+
KEY_CTRL_Z: ::Integer
|
|
61
|
+
|
|
62
|
+
KEY_ESC: ::Integer
|
|
63
|
+
|
|
64
|
+
KEY_BACKSPACE: ::Integer
|
|
65
|
+
|
|
66
|
+
KEY_RUNES: ::Integer
|
|
67
|
+
|
|
68
|
+
KEY_UP: ::Integer
|
|
69
|
+
|
|
70
|
+
KEY_DOWN: ::Integer
|
|
71
|
+
|
|
72
|
+
KEY_RIGHT: ::Integer
|
|
73
|
+
|
|
74
|
+
KEY_LEFT: ::Integer
|
|
75
|
+
|
|
76
|
+
KEY_HOME: ::Integer
|
|
77
|
+
|
|
78
|
+
KEY_END: ::Integer
|
|
79
|
+
|
|
80
|
+
KEY_PGUP: ::Integer
|
|
81
|
+
|
|
82
|
+
KEY_PGDOWN: ::Integer
|
|
83
|
+
|
|
84
|
+
KEY_DELETE: ::Integer
|
|
85
|
+
|
|
86
|
+
KEY_INSERT: ::Integer
|
|
87
|
+
|
|
88
|
+
KEY_F1: ::Integer
|
|
89
|
+
|
|
90
|
+
KEY_F2: ::Integer
|
|
91
|
+
|
|
92
|
+
KEY_F3: ::Integer
|
|
93
|
+
|
|
94
|
+
KEY_F4: ::Integer
|
|
95
|
+
|
|
96
|
+
KEY_F5: ::Integer
|
|
97
|
+
|
|
98
|
+
KEY_F6: ::Integer
|
|
99
|
+
|
|
100
|
+
KEY_F7: ::Integer
|
|
101
|
+
|
|
102
|
+
KEY_F8: ::Integer
|
|
103
|
+
|
|
104
|
+
KEY_F9: ::Integer
|
|
105
|
+
|
|
106
|
+
KEY_F10: ::Integer
|
|
107
|
+
|
|
108
|
+
KEY_F11: ::Integer
|
|
109
|
+
|
|
110
|
+
KEY_F12: ::Integer
|
|
111
|
+
|
|
112
|
+
KEY_SHIFT_TAB: ::Integer
|
|
113
|
+
|
|
114
|
+
KEY_SPACE: ::Integer
|
|
115
|
+
|
|
116
|
+
attr_reader key_type: untyped
|
|
117
|
+
|
|
118
|
+
attr_reader runes: untyped
|
|
119
|
+
|
|
120
|
+
attr_reader alt: untyped
|
|
121
|
+
|
|
122
|
+
attr_reader name: untyped
|
|
123
|
+
|
|
124
|
+
def initialize: (key_type: untyped, ?runes: untyped, ?alt: untyped, ?name: untyped) -> untyped
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def lookup_key_name: () -> untyped
|
|
129
|
+
|
|
130
|
+
public
|
|
131
|
+
|
|
132
|
+
def to_s: () -> untyped
|
|
133
|
+
|
|
134
|
+
def char: () -> untyped
|
|
135
|
+
|
|
136
|
+
def ctrl?: () -> untyped
|
|
137
|
+
|
|
138
|
+
def runes?: () -> untyped
|
|
139
|
+
|
|
140
|
+
def space?: () -> untyped
|
|
141
|
+
|
|
142
|
+
def enter?: () -> untyped
|
|
143
|
+
|
|
144
|
+
def backspace?: () -> untyped
|
|
145
|
+
|
|
146
|
+
def tab?: () -> untyped
|
|
147
|
+
|
|
148
|
+
def esc?: () -> untyped
|
|
149
|
+
|
|
150
|
+
def up?: () -> untyped
|
|
151
|
+
|
|
152
|
+
def down?: () -> untyped
|
|
153
|
+
|
|
154
|
+
def left?: () -> untyped
|
|
155
|
+
|
|
156
|
+
def right?: () -> untyped
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class MouseMessage < Message
|
|
160
|
+
BUTTON_NONE: ::Integer
|
|
161
|
+
|
|
162
|
+
BUTTON_LEFT: ::Integer
|
|
163
|
+
|
|
164
|
+
BUTTON_MIDDLE: ::Integer
|
|
165
|
+
|
|
166
|
+
BUTTON_RIGHT: ::Integer
|
|
167
|
+
|
|
168
|
+
BUTTON_WHEEL_UP: ::Integer
|
|
169
|
+
|
|
170
|
+
BUTTON_WHEEL_DOWN: ::Integer
|
|
171
|
+
|
|
172
|
+
ACTION_PRESS: ::Integer
|
|
173
|
+
|
|
174
|
+
ACTION_RELEASE: ::Integer
|
|
175
|
+
|
|
176
|
+
ACTION_MOTION: ::Integer
|
|
177
|
+
|
|
178
|
+
attr_reader x: untyped
|
|
179
|
+
|
|
180
|
+
attr_reader y: untyped
|
|
181
|
+
|
|
182
|
+
attr_reader button: untyped
|
|
183
|
+
|
|
184
|
+
attr_reader action: untyped
|
|
185
|
+
|
|
186
|
+
attr_reader shift: untyped
|
|
187
|
+
|
|
188
|
+
attr_reader alt: untyped
|
|
189
|
+
|
|
190
|
+
attr_reader ctrl: untyped
|
|
191
|
+
|
|
192
|
+
def initialize: (x: untyped, y: untyped, button: untyped, action: untyped, ?shift: untyped, ?alt: untyped, ?ctrl: untyped) -> untyped
|
|
193
|
+
|
|
194
|
+
def press?: () -> untyped
|
|
195
|
+
|
|
196
|
+
def release?: () -> untyped
|
|
197
|
+
|
|
198
|
+
def motion?: () -> untyped
|
|
199
|
+
|
|
200
|
+
def wheel?: () -> untyped
|
|
201
|
+
|
|
202
|
+
def left?: () -> untyped
|
|
203
|
+
|
|
204
|
+
def right?: () -> untyped
|
|
205
|
+
|
|
206
|
+
def middle?: () -> untyped
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class WindowSizeMessage < Message
|
|
210
|
+
attr_reader width: untyped
|
|
211
|
+
|
|
212
|
+
attr_reader height: untyped
|
|
213
|
+
|
|
214
|
+
def initialize: (width: untyped, height: untyped) -> untyped
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class FocusMessage < Message
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class BlurMessage < Message
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class QuitMessage < Message
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class ResumeMessage < Message
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def self.parse_event: (untyped hash) -> untyped
|
|
230
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Generated from lib/bubbletea/model.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Bubbletea
|
|
4
|
+
# Model module that provides the Elm Architecture interface.
|
|
5
|
+
# Include this module in your model class and implement:
|
|
6
|
+
# - init: Returns [model, command] - initial state and optional command
|
|
7
|
+
# - update(message): Returns [model, command] - new state and optional command
|
|
8
|
+
# - view: Returns String - the current view to render
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
# class Counter
|
|
12
|
+
# include Bubbletea::Model
|
|
13
|
+
#
|
|
14
|
+
# attr_reader :count
|
|
15
|
+
#
|
|
16
|
+
# def initialize
|
|
17
|
+
# @count = 0
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def init
|
|
21
|
+
# [self, nil]
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def update(message)
|
|
25
|
+
# case message
|
|
26
|
+
# when Bubbletea::KeyMessage
|
|
27
|
+
# case message.to_s
|
|
28
|
+
# when "q", "ctrl+c"
|
|
29
|
+
# [self, Bubbletea.quit]
|
|
30
|
+
# when "up", "k"
|
|
31
|
+
# @count += 1
|
|
32
|
+
# [self, nil]
|
|
33
|
+
# when "down", "j"
|
|
34
|
+
# @count -= 1
|
|
35
|
+
# [self, nil]
|
|
36
|
+
# else
|
|
37
|
+
# [self, nil]
|
|
38
|
+
# end
|
|
39
|
+
# else
|
|
40
|
+
# [self, nil]
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# def view
|
|
45
|
+
# "Count: #{@count}\n\nPress up/down to change, q to quit"
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
module Model
|
|
49
|
+
def init: () -> untyped
|
|
50
|
+
|
|
51
|
+
def update: (untyped _message) -> untyped
|
|
52
|
+
|
|
53
|
+
def view: () -> untyped
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Generated from lib/bubbletea/runner.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Bubbletea
|
|
4
|
+
# Runner manages the event loop and coordinates between the model and the terminal
|
|
5
|
+
class Runner
|
|
6
|
+
attr_reader options: untyped
|
|
7
|
+
|
|
8
|
+
DEFAULT_OPTIONS: untyped
|
|
9
|
+
|
|
10
|
+
def initialize: (untyped model, **untyped options) -> untyped
|
|
11
|
+
|
|
12
|
+
def run: () -> untyped
|
|
13
|
+
|
|
14
|
+
def send: (untyped message) -> untyped
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def setup_terminal: () -> untyped
|
|
19
|
+
|
|
20
|
+
def cleanup_terminal: () -> untyped
|
|
21
|
+
|
|
22
|
+
def setup_resize_handler: () -> untyped
|
|
23
|
+
|
|
24
|
+
def restore_resize_handler: () -> untyped
|
|
25
|
+
|
|
26
|
+
def update_terminal_size: () -> untyped
|
|
27
|
+
|
|
28
|
+
def run_loop: () -> untyped
|
|
29
|
+
|
|
30
|
+
def check_resize: () -> untyped
|
|
31
|
+
|
|
32
|
+
def process_pending_messages: () -> untyped
|
|
33
|
+
|
|
34
|
+
def handle_message: (untyped message) -> untyped
|
|
35
|
+
|
|
36
|
+
def process_command: (untyped command) -> untyped
|
|
37
|
+
|
|
38
|
+
def execute_command_sync: (untyped command) -> untyped
|
|
39
|
+
|
|
40
|
+
def execute_sequence_sync: (untyped commands) -> untyped
|
|
41
|
+
|
|
42
|
+
def execute_batch_sync: (untyped commands) -> untyped
|
|
43
|
+
|
|
44
|
+
def schedule_tick: (untyped tick_command) -> untyped
|
|
45
|
+
|
|
46
|
+
def schedule_delayed_message: (untyped send_command) -> untyped
|
|
47
|
+
|
|
48
|
+
def process_ticks: () -> untyped
|
|
49
|
+
|
|
50
|
+
def suspend_process: () -> untyped
|
|
51
|
+
|
|
52
|
+
def render: () -> untyped
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.run: (untyped model, **untyped options) -> untyped
|
|
56
|
+
end
|
data/sig/bubbletea.rbs
ADDED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bubbletea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marco Roth
|
|
@@ -52,6 +52,12 @@ files:
|
|
|
52
52
|
- lib/bubbletea/model.rb
|
|
53
53
|
- lib/bubbletea/runner.rb
|
|
54
54
|
- lib/bubbletea/version.rb
|
|
55
|
+
- sig/bubbletea.rbs
|
|
56
|
+
- sig/bubbletea/commands.rbs
|
|
57
|
+
- sig/bubbletea/messages.rbs
|
|
58
|
+
- sig/bubbletea/model.rbs
|
|
59
|
+
- sig/bubbletea/runner.rbs
|
|
60
|
+
- sig/bubbletea/version.rbs
|
|
55
61
|
homepage: https://github.com/marcoroth/bubbletea-ruby
|
|
56
62
|
licenses:
|
|
57
63
|
- MIT
|