oscparser 1.1.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.
@@ -0,0 +1,165 @@
1
+ import struct
2
+ from typing import Any, cast
3
+
4
+ from oscparser.ctx import DataBuffer
5
+ from oscparser.processing.args.args import (
6
+ ArgDispatcher,
7
+ _decode_string,
8
+ _encode_string,
9
+ create_arg_dispatcher,
10
+ )
11
+ from oscparser.processing.osc.processing import OSCDispatcher, OSCPacketHandler
12
+ from oscparser.types import OSCArg, OSCBundle, OSCMessage
13
+
14
+ _BUNDLE_PREFIX = b"#bundle\x00"
15
+
16
+
17
+ class OSCBundleHandler(OSCPacketHandler[OSCBundle]):
18
+ """Handler for OSC bundles - timetag + nested elements."""
19
+
20
+ def __init__(self, dispatcher: OSCDispatcher):
21
+ self.dispatcher = dispatcher
22
+
23
+ @classmethod
24
+ def from_dispatcher(cls, dispatcher: OSCDispatcher, arg_dispatcher: ArgDispatcher) -> "OSCBundleHandler":
25
+ return cls(dispatcher)
26
+
27
+ def decode(self, ctx: DataBuffer) -> OSCBundle:
28
+ """Decode an OSC bundle from data buffer.
29
+
30
+ Format:
31
+ - Bundle prefix "#bundle\\x00"
32
+ - Timetag (64-bit NTP timestamp)
33
+ - Elements (size + content pairs)
34
+ """
35
+ # Read and verify bundle prefix
36
+ prefix = ctx.read(len(_BUNDLE_PREFIX))
37
+ if prefix != _BUNDLE_PREFIX:
38
+ raise ValueError(f"Invalid bundle prefix: {prefix!r}")
39
+
40
+ # Read timetag (64-bit big-endian integer)
41
+ timetag = struct.unpack(">Q", ctx.read(8))[0]
42
+
43
+ # Parse bundle elements
44
+ elements: list[Any] = []
45
+
46
+ while ctx.remaining() > 0:
47
+ # Read element size (32-bit big-endian integer)
48
+ element_size = struct.unpack(">I", ctx.read(4))[0]
49
+
50
+ # Read element data
51
+ element_data = DataBuffer(ctx.read(element_size))
52
+
53
+ # Recursively decode the element using dispatcher
54
+ handler = self.dispatcher.get_handler(element_data)
55
+ element = handler.decode(element_data)
56
+
57
+ elements.append(element)
58
+
59
+ return OSCBundle(timetag=timetag, elements=tuple(elements))
60
+
61
+ def encode(self, packet: OSCBundle, buf: DataBuffer):
62
+ """Encode an OSC bundle to bytes."""
63
+ if not isinstance(packet, OSCBundle):
64
+ raise TypeError(f"Expected OSCBundle, got {type(packet)}")
65
+
66
+ # Write bundle prefix
67
+ buf.write(_BUNDLE_PREFIX)
68
+
69
+ # Write timetag
70
+ buf.write(struct.pack(">Q", packet.timetag))
71
+
72
+ # Encode each element
73
+ for element in packet.elements:
74
+ # Recursively encode element
75
+ result = DataBuffer(b"")
76
+ element_handler = self.dispatcher.get_object_handler(type(element))
77
+ element_handler.encode(element, result)
78
+ # Write element size
79
+ buf.write(struct.pack(">I", len(result.data)))
80
+
81
+ # Write element data
82
+ buf.write(result.data)
83
+
84
+
85
+ class OSCMessageHandler(OSCPacketHandler[OSCMessage]):
86
+ """Handler for OSC messages - address pattern + typed arguments."""
87
+
88
+ def __init__(self, dispatcher: OSCDispatcher, arg_dispatcher: ArgDispatcher):
89
+ self.dispatcher = dispatcher
90
+ # Use the arg dispatcher for handling individual arguments
91
+ self.arg_dispatcher = arg_dispatcher if arg_dispatcher is not None else create_arg_dispatcher()
92
+
93
+ @classmethod
94
+ def from_dispatcher(cls, dispatcher: OSCDispatcher, arg_dispatcher: ArgDispatcher) -> "OSCMessageHandler":
95
+ return cls(dispatcher, arg_dispatcher)
96
+
97
+ def decode(self, ctx: DataBuffer) -> OSCMessage:
98
+ """Decode an OSC message from data buffer.
99
+
100
+ Format:
101
+ - Address pattern (string)
102
+ - Type tag string (string starting with ',')
103
+ - Arguments (based on type tags)
104
+ """
105
+ # Parse address pattern
106
+ address = _decode_string(ctx)
107
+
108
+ # Check if there are any bytes remaining
109
+ if ctx.remaining() == 0:
110
+ # No arguments
111
+ return OSCMessage(address=address, args=())
112
+
113
+ # Parse type tag string
114
+ type_tag_string = _decode_string(ctx)
115
+
116
+ if not type_tag_string.startswith(","):
117
+ raise ValueError(f"Type tag string must start with ',': {type_tag_string!r}")
118
+
119
+ # Remove the leading comma
120
+ type_tags = type_tag_string[1:]
121
+
122
+ # Parse arguments based on type tags
123
+ args: list[OSCArg] = []
124
+
125
+ typetag_ctx = DataBuffer(type_tags.encode("utf-8"))
126
+
127
+ while typetag_ctx.remaining() > 0:
128
+ tag = typetag_ctx.read(1)
129
+
130
+ handler = self.arg_dispatcher.get_handler_by_tag(tag)
131
+ arg = cast(OSCArg, handler.decode(ctx, typetag_ctx))
132
+ args.append(arg)
133
+
134
+ return OSCMessage(address=address, args=tuple(args))
135
+
136
+ def encode(self, packet: OSCMessage, buf: DataBuffer):
137
+ """Encode an OSC message to bytes."""
138
+ if not isinstance(packet, OSCMessage):
139
+ raise TypeError(f"Expected OSCMessage, got {type(packet)}")
140
+
141
+ # Encode address pattern
142
+ buf.write(_encode_string(packet.address))
143
+
144
+ # Build type tag string and argument data
145
+ typetag_ctx = DataBuffer(b"")
146
+ args_ctx = DataBuffer(b"")
147
+
148
+ # Start type tag string with comma
149
+ typetag_ctx.write(b",")
150
+
151
+ for arg in packet.args:
152
+ handler = self.arg_dispatcher.get_handler_by_object(type(arg))
153
+ handler.encode(arg, args_ctx, typetag_ctx)
154
+
155
+ # Write type tag string
156
+ buf.write(_encode_string(typetag_ctx.data.decode("utf-8")))
157
+
158
+ # Write argument data
159
+ buf.write(args_ctx.data)
160
+
161
+
162
+ def register_osc_handlers(dispatcher: OSCDispatcher) -> None:
163
+ """Register OSC message and bundle handlers."""
164
+ dispatcher.register_handler(OSCMessage, b"/", OSCMessageHandler)
165
+ dispatcher.register_handler(OSCBundle, b"#bundle\x00", OSCBundleHandler)
@@ -0,0 +1,46 @@
1
+ from typing import Any, Protocol
2
+
3
+ from oscparser.ctx import DataBuffer
4
+ from oscparser.processing.args.args import create_arg_dispatcher
5
+ from oscparser.processing.args.proccessing import ArgDispatcher
6
+
7
+
8
+ class OSCPacketHandler[T: object = object](Protocol):
9
+ """Protocol for OSC packet handlers (messages and bundles)."""
10
+
11
+ @classmethod
12
+ def from_dispatcher(cls, dispatcher: "OSCDispatcher", arg_dispatcher: ArgDispatcher) -> "OSCPacketHandler[T]": ...
13
+
14
+ def decode(self, ctx: DataBuffer) -> T:
15
+ """Decode OSC packet from data buffer."""
16
+ ...
17
+
18
+ def encode(self, packet: T, buf: DataBuffer):
19
+ """Encode OSC packet to bytes."""
20
+ ...
21
+
22
+
23
+ class OSCDispatcher:
24
+ """Dispatcher for OSC packet types (messages and bundles)."""
25
+
26
+ def __init__(self, arg_dispatcher: ArgDispatcher | None = None):
27
+ self._handlers: dict[bytes, OSCPacketHandler[Any]] = {}
28
+ self._object_handlers: dict[type, OSCPacketHandler[Any]] = {}
29
+ self._arg_dispatcher = arg_dispatcher if arg_dispatcher is not None else create_arg_dispatcher()
30
+
31
+ def register_handler[T: object](self, obj: type[T], handler_tag: bytes, handler: type[OSCPacketHandler[T]]) -> None:
32
+ """Register a packet handler."""
33
+ handler_inst = handler.from_dispatcher(self, self._arg_dispatcher)
34
+ self._handlers[handler_tag] = handler_inst
35
+ self._object_handlers[obj] = handler_inst
36
+
37
+ def get_handler(self, data: DataBuffer) -> OSCPacketHandler:
38
+ """Get appropriate handler for the given data."""
39
+ for handler_tag, handler in self._handlers.items():
40
+ if data.startswith(handler_tag):
41
+ return handler
42
+ raise ValueError("No handler found")
43
+
44
+ def get_object_handler[T](self, obj: type[T]) -> OSCPacketHandler[T]:
45
+ """Get handler for the given object type."""
46
+ return self._object_handlers[obj]
oscparser/types.py ADDED
@@ -0,0 +1,263 @@
1
+ """OSC 1.0 / 1.1 data types.
2
+
3
+ This module defines Python classes and type aliases that model the
4
+ Open Sound Control 1.0 atomic types, composite types (messages and
5
+ bundles), and the additional recommended argument tags listed in the
6
+ specification (often referred to as "OSC 1.1").
7
+
8
+ It does **not** implement parsing or encoding; it is purely types.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from datetime import datetime
14
+ from typing import ClassVar, Tuple, Union
15
+
16
+ from pydantic import BaseModel
17
+
18
+
19
+ class _FrozenModel(BaseModel):
20
+ """Base class for immutable OSC value types."""
21
+
22
+ class Config:
23
+ frozen = True
24
+
25
+
26
+ class OSCInt(_FrozenModel):
27
+ """32-bit signed integer argument (tag 'i')."""
28
+
29
+ TAG: ClassVar[str] = "i"
30
+ value: int
31
+
32
+ def __init__(self, value: int) -> None:
33
+ super().__init__(value=value) # type: ignore
34
+
35
+
36
+ class OSCFloat(_FrozenModel):
37
+ """32-bit IEEE 754 floating point argument (tag 'f')."""
38
+
39
+ TAG: ClassVar[str] = "f"
40
+ value: float
41
+
42
+ def __init__(self, value: float) -> None:
43
+ super().__init__(value=value) # type: ignore
44
+
45
+
46
+ class OSCString(_FrozenModel):
47
+ """OSC string argument (tag 's')."""
48
+
49
+ TAG: ClassVar[str] = "s"
50
+ value: str
51
+
52
+ def __init__(self, value: str) -> None:
53
+ super().__init__(value=value) # type: ignore
54
+
55
+
56
+ class OSCBlob(_FrozenModel):
57
+ """OSC blob argument (tag 'b')."""
58
+
59
+ TAG: ClassVar[str] = "b"
60
+ value: bytes
61
+
62
+ def __init__(self, value: bytes) -> None:
63
+ super().__init__(value=value) # type: ignore
64
+
65
+
66
+ class OSCTrue(_FrozenModel):
67
+ """Boolean true argument (tag 'T')."""
68
+
69
+ TAG: ClassVar[str] = "T"
70
+
71
+
72
+ class OSCFalse(_FrozenModel):
73
+ """Boolean false argument (tag 'F')."""
74
+
75
+ TAG: ClassVar[str] = "F"
76
+
77
+
78
+ class OSCNil(_FrozenModel):
79
+ """Nil / null argument (tag 'N')."""
80
+
81
+ TAG: ClassVar[str] = "N"
82
+
83
+
84
+ class OSCInt64(_FrozenModel):
85
+ """64-bit signed integer argument (tag 'h')."""
86
+
87
+ TAG: ClassVar[str] = "h"
88
+ value: int
89
+
90
+ def __init__(self, value: int) -> None:
91
+ super().__init__(value=value) # type: ignore
92
+
93
+
94
+ class OSCDouble(_FrozenModel):
95
+ """64-bit IEEE 754 floating point argument (tag 'd')."""
96
+
97
+ TAG: ClassVar[str] = "d"
98
+ value: float
99
+
100
+ def __init__(self, value: float) -> None:
101
+ super().__init__(value=value) # type: ignore
102
+
103
+
104
+ class OSCTimeTag(_FrozenModel):
105
+ """OSC timetag argument (tag 't'), represented as a datetime."""
106
+
107
+ TAG: ClassVar[str] = "t"
108
+ value: datetime
109
+
110
+ def __init__(self, value: datetime) -> None:
111
+ super().__init__(value=value) # type: ignore
112
+
113
+
114
+ class OSCChar(_FrozenModel):
115
+ """Single ASCII / UTF-8 character argument (tag 'c')."""
116
+
117
+ TAG: ClassVar[str] = "c"
118
+ value: str # usually length 1
119
+
120
+ def __init__(self, value: str) -> None:
121
+ super().__init__(value=value) # type: ignore
122
+
123
+
124
+ class OSCSymbol(_FrozenModel):
125
+ """Symbol argument (tag 'S'), semantically distinct from a string."""
126
+
127
+ TAG: ClassVar[str] = "S"
128
+ value: str
129
+
130
+ def __init__(self, value: str) -> None:
131
+ super().__init__(value=value) # type: ignore
132
+
133
+
134
+ class OSCRGBA(_FrozenModel):
135
+ """RGBA color argument (tag 'r')."""
136
+
137
+ TAG: ClassVar[str] = "r"
138
+ r: int
139
+ g: int
140
+ b: int
141
+ a: int
142
+
143
+
144
+ class OSCMidi(_FrozenModel):
145
+ """MIDI message argument (tag 'm').
146
+
147
+ Bytes from MSB to LSB are:
148
+ - port_id
149
+ - status
150
+ - data1
151
+ - data2
152
+ """
153
+
154
+ TAG: ClassVar[str] = "m"
155
+
156
+ port_id: int
157
+ status: int
158
+ data1: int
159
+ data2: int
160
+
161
+
162
+ class OSCImpulse(_FrozenModel):
163
+ """Impulse / infinitum / bang argument (tag 'I').
164
+
165
+ There is no payload; the presence of this value is the data.
166
+ """
167
+
168
+ TAG: ClassVar[str] = "I"
169
+
170
+ # No fields
171
+ pass
172
+
173
+
174
+ # Singleton instance that can be reused for all impulse arguments.
175
+ OSC_IMPULSE = OSCImpulse()
176
+
177
+
178
+ class OSCArray(_FrozenModel):
179
+ """Array argument (tags '[' ... ']').
180
+
181
+ Contains a sequence of other OSC arguments, which may themselves be
182
+ arrays (nested arrays are allowed by the 1.0 spec).
183
+ """
184
+
185
+ OPEN_TAG: ClassVar[str] = "["
186
+ CLOSE_TAG: ClassVar[str] = "]"
187
+ items: tuple["OSCArg", ...]
188
+
189
+ def __init__(self, items: tuple["OSCArg", ...]) -> None:
190
+ super().__init__(items=items) # type: ignore
191
+
192
+
193
+ # === Composite packet types (messages and bundles) ===
194
+
195
+
196
+ OSCAtomic = Union[
197
+ OSCInt,
198
+ OSCFloat,
199
+ OSCString,
200
+ OSCBlob,
201
+ OSCTrue,
202
+ OSCFalse,
203
+ OSCNil,
204
+ OSCInt64,
205
+ OSCDouble,
206
+ OSCTimeTag,
207
+ OSCChar,
208
+ OSCSymbol,
209
+ OSCRGBA,
210
+ OSCMidi,
211
+ OSCImpulse,
212
+ ]
213
+
214
+ OSCArg = Union[OSCAtomic, OSCArray]
215
+
216
+
217
+ class OSCMessage(_FrozenModel):
218
+ """OSC message: address pattern + typed argument list.
219
+
220
+ - ``address`` is an OSC Address Pattern beginning with '/'.
221
+ - ``args`` is a sequence of OSCArg values.
222
+ """
223
+
224
+ address: str
225
+ args: Tuple[OSCArg, ...]
226
+
227
+
228
+ class OSCBundle(_FrozenModel):
229
+ """OSC bundle containing messages and/or sub-bundles.
230
+
231
+ - ``timetag`` is a 64-bit OSC timetag (NTP). 0 means "immediately".
232
+ - ``elements`` is a sequence of OSCMessage or nested OSCBundle.
233
+ """
234
+
235
+ timetag: int
236
+ elements: Tuple[OSCPacket, ...]
237
+
238
+
239
+ OSCPacket = Union["OSCMessage", "OSCBundle"]
240
+
241
+ __all__ = [
242
+ "OSCRGBA",
243
+ "OSC_IMPULSE",
244
+ "OSCArg",
245
+ "OSCArray",
246
+ "OSCAtomic",
247
+ "OSCBlob",
248
+ "OSCBundle",
249
+ "OSCChar",
250
+ "OSCDouble",
251
+ "OSCFalse",
252
+ "OSCImpulse",
253
+ "OSCInt",
254
+ "OSCInt64",
255
+ "OSCMessage",
256
+ "OSCMidi",
257
+ "OSCNil",
258
+ "OSCPacket",
259
+ "OSCString",
260
+ "OSCSymbol",
261
+ "OSCTimeTag",
262
+ "OSCTrue",
263
+ ]
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: oscparser
3
+ Version: 1.1.0
4
+ Summary: Library for parsing and building Open Sound Control (OSC) messages and bundles.
5
+ Author-email: Morph Tollon <code@morphtollon.co.uk>, Ripple <github@ripple.contact>
6
+ Maintainer-email: Morph Tollon <code@morphtollon.co.uk>, Ripple <github@ripple.contact>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2025 Morph Tollon
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Repository, https://gitlab.com/Theatre-Tools/python-osc-parser
30
+ Project-URL: Homepage, https://gitlab.com/Theatre-Tools/python-osc-parser
31
+ Project-URL: Issues, https://gitlab.com/Theatre-Tools/python-osc-parser/-/issues
32
+ Keywords: osc,sound,midi,music
33
+ Classifier: Development Status :: 5 - Production/Stable
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: Freely Distributable
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Topic :: Multimedia :: Sound/Audio
38
+ Classifier: Topic :: System :: Networking
39
+ Requires-Python: <3.14,>=3.13
40
+ Description-Content-Type: text/markdown
41
+ License-File: LICENSE
42
+ Dynamic: license-file
43
+
44
+ # Python OSC Parser
45
+
46
+ Open Sound Control (OSC) protocol implementation in Python.
47
+ ## Current status
48
+
49
+ This library was developed following the
50
+ [OpenSoundControl Specification 1.0](https://opensoundcontrol.stanford.edu/spec-1_0.html)
51
+ and is currently in a stable state.
52
+
53
+ ## Features
54
+
55
+ - Parsing and constructing OSC messages and bundles.
56
+ - Support for various OSC data types including integers, floats, strings, blobs, and more.
57
+
58
+ ## License
59
+
60
+ This software is licensed under the MIT License. See the LICENSE file for details.
@@ -0,0 +1,19 @@
1
+ oscparser/__init__.py,sha256=f9TDvJEJTxoZCtOGzTA8B1dccER7nAfiiEr3ra_wAGM,1501
2
+ oscparser/ctx.py,sha256=uAt3DkXVM39xXlJFo7HVfolj9-MM9zdbEDQH6Ar5I-8,429
3
+ oscparser/decode.py,sha256=W9B9jcwz9-dKvjjA-gU-1Jt4wLp6QfVzX5_S0X6AIao,2765
4
+ oscparser/encode.py,sha256=jUsXyYP5L4mfGJiaihzhdtC4UWK39QpA0RU2D0epKhw,2316
5
+ oscparser/types.py,sha256=t04imRO6AjtKWzdzR8UrXKtkpSFOC6CyZq396LYdQiQ,5495
6
+ oscparser/framing/__init__.py,sha256=yjrteSCY0j9mBTbpp6sEoD5ns-sMvvsXgQz9lGs9MT8,207
7
+ oscparser/framing/framer.py,sha256=nG9yldYQenKijmqjjQRolRlYr1ZeQ3bD5AczISrkcKc,450
8
+ oscparser/framing/fullframer.py,sha256=QsNUeItdxDHEJU4fyBfGN0ghn4U75u2T0ctX71q7XF0,1069
9
+ oscparser/framing/osc10.py,sha256=gY5wOGL40v0IPuy9HjQY7j0uf3xp7ZYCOAcFmpAp0wU,2039
10
+ oscparser/framing/osc11.py,sha256=6MiM5Z1H2vcc6fsSRshbNYylTTKFx2GDlGa-Wyt__AI,6527
11
+ oscparser/processing/args/args.py,sha256=zOKMgjLixFK7n_jS3kpK-37ip9Gkzyp9ZpLGx38A_Ho,17955
12
+ oscparser/processing/args/proccessing.py,sha256=ZxjghxPHgAfBMS5QoJ16vrnMTAyCTweTP5ykrctxglY,1045
13
+ oscparser/processing/osc/handlers.py,sha256=_ExoQEM5XdLP6r2L0W7z-qfE1SzvAKDgWUCZdQO5xJ8,5645
14
+ oscparser/processing/osc/processing.py,sha256=3-LOXVteJLf8TYlHX-FyG2McZLVSQdyiWBbJoAEEDwY,1884
15
+ oscparser-1.1.0.dist-info/licenses/LICENSE,sha256=bhC-qZPLBnE1KaT__cf0Zw7qXl1xyU9qdhvVHkIeEhg,1069
16
+ oscparser-1.1.0.dist-info/METADATA,sha256=zMFTQ-AiQa67z63Mpsvi7RP7dZgv_HX5Inw1FE5JbGo,2740
17
+ oscparser-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ oscparser-1.1.0.dist-info/top_level.txt,sha256=3bs-r_OX2twJ4h00vGo3elKFzAhJeQ8UIN1TT73AR0E,10
19
+ oscparser-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Morph Tollon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ oscparser